一、数据安全保护
该功能为了保护数据库配置及数据安全,在一定的程度上控制开发人员流动导致敏感信息泄露。
- 3.3.2 开始支持
- 配置安全
YML 配置:
// 加密配置 mpw: 开头紧接加密内容( 非数据库配置专用 YML 中其它配置也是可以使用的 )
spring:datasource:url: mpw:qRhvCwF4GOqjessEB3G+a5okP+uXXr96wcucn2Pev6Bf1oEMZ1gVpPPhdDmjQqoMpassword: mpw:Hzy5iliJbwDHhjLs1L0j6w==username: mpw:Xb+EgsyuYRXw7U7sBJjBpA==
密钥加密:
// 生成 16 位随机 AES 密钥
String randomKey = AES.generateRandomKey();// 随机密钥加密
String result = AES.encrypt(data, randomKey);
如何使用:
// Jar 启动参数( idea 设置 Program arguments , 服务器可以设置为启动环境变量 )
--mpw.key=d1104d7c3b616f0b
- 数据安全:
👉 字段加密解密(opens new window)
👉 字段脱敏(opens new window)
- SQL 注入安全保护说明
Wrappers.query()
// 开启自动检查 SQL 注入
.checkSqlInjection().orderByDesc("任意前端传入字段")
// 手动校验方式
SqlInjectionUtils.check("任意前端传入字段")
注意!
- 加密配置必须以 mpw: 字符串开头
- 随机密钥请负责人妥善保管,当然越少人知道越好。
二、批量操作
- 3.5.4 + 版本支持
- 事务需要手动自行控制(默认false)
- 注意
- autoCommit参数在Spring项目下是无效的,具体见SpringManagedTransactionFactory,在原生mybatis下可用
- 执行返回值为批量处理结果,业务可根据返回值判断是否成功
- 数据能否写入或更新取决于代码能否正确执行到flushStatements
- 支持Spring与非Spring项目使用
- 执行抛出异常为PersistenceException
- saveOrUpdate是个比较有争议的方法,个人建议批量操作保持为简单的新增或更新操作
1、类说明
1.1 MybatisBatch<?>
- 泛型为具体 实际数据类型
- sqlSessionFactory可通过容器获取,非Spring容器下可在自行初始化Mybatis时将上下文记录起来
- dataList为实际的批量数据处理 (非空)
1.2 MybatisBatch.Method<?>
实际为BatchMethod, 封装了框架内部操作方法简化调用.
- 泛型为 实际Mapper方法参数需要的类型
- mapperClass为具体的实际Mapper类
#BatchMethod<?>
- 泛型为 实际Mapper方法参数需要的类型
- statementId : 执行的MappedStatement
- parameterConvert: 参数类型转换处理器,当数据类型与实际mapper方法参数不一致时,可以通过类型转换器进行转换
2、使用说明
- 构建MybatisBatch (将数据与sqlSessionFactory绑定起来)
- 构建MybatisBatch.Method (确定执行执行Mapper类方法)
- 执行操作 (执行处理操作,将批量参数转换为实际mapper需要的参数)
3、返回值说明
List<BatchResult>
执行返回每次按照执行MappedStatement + sql分组返回的操作结果
注意
例如批量根据id更新:
假设10条数据, 5条更新一个字段 5条更新两个字段 , 那返回值就是容量为2的List, 里面各存储了五条记录更新情况
update table set c1 = ? where id = ? //只更新一个字段
update table set c1 = ? , c2 = ? where id = ? // 只更新两个字段
mappedStatement: 执行的MappedStatement
sql: 执行sql
parameterObjects: 参数列表
updateCounts[]: 影响行数(与上面的parameterObjects数据一一对应)
4、使用示例
框架提供了一个MybatisBatchUtils进行静态方法调用.
4.1 execute
适用于insert,update,delete操作
示例一: 数据类型为实体
List<H2User> userList = Arrays.asList(new H2User(2000L, "测试"), new H2User(2001L, "测试"));
MybatisBatch<H2User> mybatisBatch = new MybatisBatch<>(sqlSessionFactory, userList);
MybatisBatch.Method<H2User> method = new MybatisBatch.Method<>(H2UserMapper.class);
mybatisBatch.execute(method.insert());
示例二: 数据类型为非实体
List<Long> ids = Arrays.asList(120000L, 120001L);
MybatisBatch<Long> mybatisBatch = new MybatisBatch<>(sqlSessionFactory, ids);
MybatisBatch.Method<H2User> method = new MybatisBatch.Method<>(H2UserMapper.class);
mybatisBatch.execute(method.insert(id -> {// 将id转换为实体H2User h2User = new H2User();h2User.setTestId(id);return h2User;
}));
示例三: 自定义方法插入(无注解)
// mapper方法(方法参数无注解)
@Insert("insert into h2user(name,version) values( #{name}, #{version})"
)
int myInsertWithoutParam(H2User user1);// 准备数据集合
List<H2User> h2UserList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {h2UserList.add(new H2User("myInsertWithoutParam" + i));
}MybatisBatch<H2User> mybatisBatch = new MybatisBatch<>(sqlSessionFactory, h2UserList);
MybatisBatch.Method<H2User> method = new MybatisBatch.Method<>(H2UserMapper.class);
mybatisBatch.execute(method.get("myInsertWithoutParam"));
示例四: 自定义方法插入(注解)
// 方法参数带注解
@Insert("insert into h2user(name,version) values( #{user1.name}, #{user1.version})"
)
int myInsertWithParam(@Param("user1") H2User user1);// 准备数据集合
List<H2User> h2UserList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {h2UserList.add(new H2User("myInsertWithParam" + i));
}MybatisBatch<H2User> mybatisBatch = new MybatisBatch<>(sqlSessionFactory, h2UserList);
MybatisBatch.Method<H2User> method = new MybatisBatch.Method<>(H2UserMapper.class);
mybatisBatch.execute(method.get("myInsertWithParam", (user) -> {// 转换成mapper方法参数Map<String, Object> map = new HashMap<>();map.put("user1", user);return map;
}));
4.2 saveOrUpdate
执行保存或更新
注意
重点注意跨sqlSession下缓存和数据感知问题
saveOrUpdate(BatchMethod<T> insertMethod, BiPredicate<BatchSqlSession, T> insertPredicate, BatchMethod<T> updateMethod)
- insertMethod: 指定insert操作处理
- insertPredicate: 指定insert操作处理条件
注意
注意这里的BatchSqlSession,使用这个进行查询操作会每次都执行一次flushStatements.
如果在一次处理中,如果有两条记录相同的数据,在跨sqlSession中会执行两次插入导致主键冲突,而共享sqlsesion下会执行一次插入和一次更新
- updateMethod: 指定update操作处理
跨sqlSession
@Autowired
private H2UserMapper userMapper;List<H2User> h2UserList = new ArrayList<>();
for (int i = 0; i < 100; i++) {h2UserList.add(new H2User(Long.valueOf(40000 + i), "test" + i));
}
MybatisBatch.Method<H2User> mapperMethod = new MybatisBatch.Method<>(H2UserMapper.class);new MybatisBatch<>(sqlSessionFactory,h2UserList).saveOrUpdate(mapperMethod.insert(), // 指定insert方法((sqlSession, h2User) -> userMapper.selectById(h2User.getTestId()) == null), //判断条件,引用另个mapper方法mapperMethod.updateById()); // 指定update方法
共用sqlSession
List<H2User> h2UserList = new ArrayList<>();
for (int i = 0; i < 100; i++) {h2UserList.add(new H2User(Long.valueOf(50000 + i), "test" + i));
}
MybatisBatch.Method<H2User> mapperMethod = new MybatisBatch.Method<>(H2UserMapper.class);new MybatisBatch<>(sqlSessionFactory,h2UserList).saveOrUpdate(mapperMethod.insert(), // 指定insert方法((sqlSession, h2User) -> sqlSession.selectList(mapperMethod.get("selectById").getStatementId(), h2User.getTestId()).isEmpty()), //判断条件,共用sqlSessionmapperMethod.updateById()); // 指定update方法
4.3 事务处理
Spring事务处理示例一
@Autowired
private TransactionTemplate transactionTemplate;transactionTemplate.execute((TransactionCallback<List<BatchResult>>) status -> {MybatisBatch.Method<H2User> mapperMethod = new MybatisBatch.Method<>(H2UserMapper.class);// 执行批量插入MybatisBatchUtils.execute(sqlSessionFactory, h2UserList, mapperMethod.insert());throw new RuntimeException("出错了");
});