闽公网安备 35020302035485号
| 标签 | 说明 |
|---|---|
| #{} | 预处理语句参数,用于绑定变量值,防止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);
}