标签 | 说明 |
---|---|
#{} | 预处理语句参数,用于绑定变量值,防止SQL注入 |
${} | 直接拼接SQL语句,不进行参数绑定,存在SQL注入风险 |
select | 用于指定查询语句,通常包含一个返回结果的select语句 |
insert | 用于指定插入数据的SQL语句 |
update | 用于指定更新数据的SQL语句 |
delete | 用于指定删除数据的SQL语句 |
resultType | 指定查询结果的类型,通常与实体类对应 |
resultMap | 用于映射查询结果到对象模型,支持嵌套结果映射 |
if | 根据条件判断是否包含某段SQL语句,使用test属性指定判断条件 |
choose/when/otherwise | 根据条件选择不同的SQL语句片段,类似于switch语句 |
foreach | 遍历集合或数组,生成SQL语句片段,常用于批量操作或生成动态SQL |
trim | 用于修剪SQL语句,指定需要保留的部分,可结合prefix和suffix使用 |
where | 生成WHERE子句,可结合prefix和suffix使用,用于拼接条件判断语句 |
set | 生成SET子句,常用于UPDATE操作中指定更新的列 |
foreach | 遍历集合或数组,生成SQL语句片段,常用于批量操作或生成动态SQL |
<select id="getUser" parameterType="map" resultType="User"> SELECT * FROM user <if test="name != null"> AND name = #{name} </if> <if test="age != null"> AND age = #{age} </if> </select>上述示例中,根据输入参数的name和age是否为空,生成相应的SQL语句。
<select id="getUser" parameterType="map" resultType="User"> SELECT * FROM user <choose> <when test="name != null"> AND name = #{name} </when> <when test="age != null"> AND age = #{age} </when> <otherwise> AND is_active = 1 </otherwise> </choose> </select>上述示例中,根据输入参数的name和age是否为空,选择不同的SQL语句片段。如果都为空,则默认使用is_active = 1作为条件。
<select id="getUsersByRole" parameterType="map" resultType="User"> SELECT * FROM user WHERE role IN <foreach item="role" index="index" collection="roles" open="(" separator="," close=")"> #{role} </foreach> </select>上述示例中,根据输入参数roles集合中的元素,生成相应的SQL语句,用于查询符合指定角色的用户。除了上述示例中的标签外,MyBatis还提供了其他标签,如<trim>、<where>、<set>等,用于更灵活地构建SQL语句。
SqlSessionFactoryBuilder 作为整个 Mybatis 的入口,提供建造者工厂,包装 XML 解析处理,并返回对应 SqlSessionFactory 处理类。通过解析把 XML 信息注册到 Configuration 配置类中,再通过传递 Configuration 配置类到各个逻辑处理类里,包括 DefaultSqlSession 中,这样就可以在获取映射器和执行SQL的时候,从配置类中拿到对应的内容了。
@Configuration public class MybatisPlusConfig { @Value("${custom.sql1:}") public String sql1; @Bean public PerformanceInterceptor performanceInterceptor() { PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); /* <!-- SQL 执行性能分析,开发环境使用,线上不推荐。maxTime 指的是 sql 最大执行时长 --> */ performanceInterceptor.setMaxTime(1000); /* <!--SQL是否格式化 默认false--> */ performanceInterceptor.setFormat(true); return performanceInterceptor; } /** * 配置分页插件 * 堆代码 duidaima.com * @return */ @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } /** * 引入业务库数据源配置 */ @Resource(name = "businessDataSource") private DataSource businessDataSource; @Bean public SqlSessionFactory businessSessionFactory() throws Exception { MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean(); sessionFactory.setDataSource(businessDataSource); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); sessionFactory.setMapperLocations(resolver.getResources("classpath*:/mapper/business/**/*.xml")); // 将配置添加至sessionFactory Properties properties = new Properties(); properties.put("sql1", sql1.replaceAll("#'\\{([^'}]+)}'", "#\\{$1\\}")); sessionFactory.setConfigurationProperties(properties); //添加分页功能 sessionFactory.setPlugins(new Interceptor[]{ paginationInterceptor() }); //map接收返回值值为null的问题,默认是当值为null,将key返回 MybatisConfiguration configuration = new MybatisConfiguration(); configuration.setCallSettersOnNulls(true); configuration.setJdbcTypeForNull(JdbcType.NULL); //打印sql 拦截器 MybatisSqlLoggerInterceptor interceptor = new MybatisSqlLoggerInterceptor(); configuration.addInterceptor(interceptor); sessionFactory.setConfiguration(configuration); //设置全局GlobaleConfig用于解决Oracle主键自增 if (businessDataSource.getDriverClassName().contains("OracleDriver")) { //设置全局GlobaleConfig用于解决Oracle主键自增 GlobalConfig.DbConfig config = new GlobalConfig.DbConfig(); config.setKeyGenerator(keyGenerator()); GlobalConfig globalConfig = new GlobalConfig(); globalConfig.setDbConfig(config); sessionFactory.setGlobalConfig(globalConfig); } return sessionFactory.getObject(); } @Bean(name = "txManager1") public PlatformTransactionManager txManager1() { return new DataSourceTransactionManager(businessDataSource); } /*** * 设置oracle主键自增 * @return */ @Bean(name = "keyGenerator") public IKeyGenerator keyGenerator() { return new OracleKeyGenerator(); } }2.编写mapper.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.example.dao.business.EntMapper"> <select id="sql1" resultType="org.example.entity.business.User" > ${sql1} </select> </mapper>仔细的小伙伴会发现这里使用了$,获取SqlSessionFactory里Properties属性值,只能用$,#获取不到,这也为后面漏洞扫描埋下了伏笔
custom: sql1: "select * from user where name = #'{name}' and age = #'{age}'"4.漏洞说明:
注解 | 作用 |
---|---|
@SelectProvider | 用于动态生成查询SQL语句 |
@InsertProvider | 用于动态生成新增SQL语句 |
@UpdateProvider | 用于动态生成更新SQL语句 |
@DeleteProvider | 用于动态生成删除SQL语句 |
@Component public class MybatisHelp { public static String sql_1; @Value("${custom.sql1:}") private void setSql1(String sql1){ sql1 = sql1.replaceAll("#'\\{([^']+)}'", "#\\{$1\\}"); sql_1 = sql1; }; public String sql_1(String name,Integer age){ return sql_1; } }这里只所以使用@Value给静态变量注入值,是因为静态变量被所有类实例对象所共享,在内存中只有一个副本,当且仅当在类初次加载时会被初始化,@XXXProvider在启动时就会加载相应的类和方法获取SQL,直接采用@Value方法是获取不到配置项的
@Mapper public interface UserMapper extends BaseMapper<User> { @SelectProvider(type = MybatisHelp.class, method = "sql_1") List<User> listParam(@Param("name") String name,@Param("age") Integer age); }