一、typeHandler 的使用
1、存储json格式字段
如果字段需要存储为json格式,可以使用JacksonTypeHandler处理器。使用方式非常简单,如下所示:
只需要加上两个注解即可:
@TableName(autoResultMap = true)
表示自动映射resultMap
@TableField(typeHandler = JacksonTypeHandler.class)
表示将UserInfo对象转为json对象入库
2、自定义 typeHandler 实现类
例如当我们 某个字段存储的类型为List或者Map时,我们可以自定义一个TypeHandler,以 list 为例,我们想存储一个字段类型为 list ,在数据库中的存储的格式是 多条数据以逗号分割,当查询时会自动根据逗号分割成列表格式。
需要实现BaseTypeHandler
抽象类:
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes({List.class})
public class ListTypeHandler extends BaseTypeHandler<List<String>> {
private static final String DELIM = ",";
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, List<String> strings, JdbcType jdbcType) throws SQLException {
String value = StringUtils.collectionToDelimitedString(strings, DELIM);
preparedStatement.setString(i, value);
}
@Override
public List<String> getNullableResult(ResultSet resultSet, String s) throws SQLException {
String value = resultSet.getString(s);
return Arrays.asList(StringUtils.tokenizeToStringArray(value, DELIM));
}
@Override
public List<String> getNullableResult(ResultSet resultSet, int i) throws SQLException {
String value = resultSet.getString(i);
return Arrays.asList(StringUtils.tokenizeToStringArray(value, DELIM));
}
@Override
public List<String> getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
String value = callableStatement.getString(i);
return Arrays.asList(StringUtils.tokenizeToStringArray(value, DELIM));
}
}
- @MappedJdbcTypes:表示
SQL
语句中查出来的类型; - @MappedTypes:表示要转成 Java 对象的类型;
- DELIM:表示字符串的分隔符,如果你是用空格分开的就赋值为空格。
-
setNonNullParameter(插入时设置参数类型)
-
getNullableResult(获取时转换回的自定义类型)
根据列名获取
resultSet:结果集
columnName:列名
public UserInfo getNullableResult(ResultSet resultSet, String columnName)
根据索引位置获取
resultSet:结果集
columnIndex:列索引
public UserInfo getNullableResult(ResultSet resultSet, int columnIndex)
根据存储过程获取
callableStatement:结果集
columnIndex:列索引
public UserInfo getNullableResult(CallableStatement callableStatement, int columnIndex)
然后再字段上指定该实现了即可:
@TableField(typeHandler = ListTypeHandler.class)
private List<String> job;
最后在数据库中存储格式为:
3、存储自定义对象字段
例如我们刚才使用json格式存储 Unit 字段,如果不加 @TableField(typeHandler = JacksonTypeHandler.class)
就会报错,因为 typeHandler 可以指定我们在Java实体类所包含的自定义类型存入数据库后的类型是什么,也可以从数据库中取出该数据后自动转换为我们自定义的Java类型。
我们虽然在 Java中定义了 Unit 类型,但是数据库不认识,我们现在就需要将 Unit 转换为数据库认识的类型。Java自带的类型在存取的时候不会出错,我们自定义的类型就会出错 是因为 mybatis已经将这些类型的TypeHandler提前写好了:
所以如果我们要存储 Unit 类型的字段,又不想用 默认的json 格式,我们也需要自定义一个 关于Unit 的 TypeHandler,如下:
public class JsonUtils {
private static ObjectMapper objectMapper = new ObjectMapper();
//初始化相关的配置
static {
//只引用不为空的值
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
//取消默认转换timestemp
objectMapper.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false);
//忽略空bean转换错误
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
//忽略在json中存在,在java对象不存在的错误
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 解决jackson2无法反序列化LocalDateTime的问题
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule());
}
/**
* 将java对象转换成json字符串
*
* @param obj
* java 对象
* @param <T>
* @return
*/
public static <T> String objectToString(T obj) {
if (obj == null) {
return null;
}
try {
return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
/**
* 将json字符串转换成java对象
*
* @param json
* 字符串
* @param tClass
* 要转换的对象
* @param <T>
* @return
*/
public static <T> T getObjetFormString(String json, Class<T> tClass) {
if (StringUtils.isBlank(json) || tClass == null) {
return null;
}
try {
return tClass.equals(String.class) ? (T) json : objectMapper.readValue(json, tClass);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* 将字符串转换成java对象
* @param json
* 字符串
* @param tTypeReference
* 对象
*
* @param <T>
* @return
*/
public static <T> T fromString(String json, TypeReference<T> tTypeReference){
if (StringUtils.isBlank(json) || tTypeReference == null) {
return null;
}
try {
return tTypeReference.getType().equals(String.class) ? (T) json : objectMapper.readValue(json, tTypeReference);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* 将json字符串转换成java集合对象
* @param json
* 字符串
* @param collectionClass
* 集合类型
* @param elementClazzes
* 成员类型
* @param <T>
* @return
*/
public static <T> T fromString(String json, Class<?> collectionClass, Class<?> ... elementClazzes){
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClazzes);
try {
return objectMapper.readValue(json, javaType);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
@MappedTypes(UserInfo.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class UserInfoTypeHandler extends BaseTypeHandler<UserInfo> {
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, UserInfo userInfo, JdbcType jdbcType) throws SQLException {
preparedStatement.setString(i, JsonUtils.objectToString(userInfo));
}
@Override
public UserInfo getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
return JsonUtils.fromString(resultSet.getString(columnName),UserInfo.class);
}
@Override
public UserInfo getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
return JsonUtils.fromString(resultSet.getString(columnIndex),UserInfo.class);
}
@Override
public UserInfo getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
return
JsonUtils.fromString(callableStatement.getString(columnIndex),UserInfo.class);
}
}
其实还是将该对象转成了 json ,但是只不过转成 json 的工具类可以 由我们自己指定。
二、mybatisplus 存储枚举类型
存储枚举类型有两种形式,第一种就是自定义 typeHandler 跟上面一样,
还有一种是使用mybatis提供的默认的处理类,如下所所示:
数据库表结构:
mybatisPlus对枚举处理器进行了补充:
创建枚举类:
@Getter
public enum OrderState {
NORMAL(0, "正常"),
CANCEL(1, "取消"),
DELETE(2, "删除");
// 状态码
@EnumValue // 用于数据库存储
private Integer code;
// 描述
@JsonValue // 用于序列化返回的json数据
private String desc;
OrderState(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
}
TOrder实体类:
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_order")
public class TOrder implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "order_id", type = IdType.AUTO)
private Integer orderId;
@TableField("user_id")
private Integer userId;
@TableField("price")
private BigDecimal price;
@TableField("status")
private OrderState status;
}
在配置文件中配置统一的枚举处理器,实现类型转换:
调用:
三、使用CommandLineRunner 修改 JacksonTypeHandler 实现类
@Component
public class JsonTypeHandlerConfig implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
JacksonTypeHandler.setObjectMapper(JsonUtils.getMapper());
}
}
四、静态工具类Db
因为实际开发中,各种Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db
使用前提是 mybatisplus的版本在 3.5.3 以上,不然没有引入。
使用示例:
//select id,name,age from user where name like '%zhang%' and age>10
QueryWrapper<User> userQuery=new QueryWrapper<User>()
.select("id", "name", "age")
.like("name","zhang")
.gt("age",10);
List<User> users=userMapper.selectList(userQuery);
List<Address> addresses = Db.lambdaQuery(Address.class)
.eq(Address::getUserId, userId)
.list();
@Test
void testDbGet() {
User user = Db.getById(1L, User.class);
System.out.println(user);
}
@Test
void testDbList() {
// 利用Db实现复杂条件查询
List<User> list = Db.lambdaQuery(User.class)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000)
.list();
}
@Test
void testDbUpdate() {
Db.lambdaUpdate(User.class)
.set(User::getBalance, 2000)
.eq(User::getUsername, "Rose");
}
五、IPage的泛型转换(entity转换为vo)
就是将查出来的实体类转换成VO类,也可以使用循环拷贝,也可以使用下面的方法:
Page<User> userPage = new Page<>(1, 10);
Page<User> page = userService.page(userPage);
IPage<UserVO> convert = page.convert(item -> Convert.convert(UserVO.class,item));
Page<User> userPage = new Page<>(1, 10);
Page<User> page = userService.page(userPage);
IPage<UserVO> convert = page.convert(item -> {
UserVO userVO = new UserVO();
BeanUtils.copyProperties(item, userVO);
userVO.setSerial(1);
return userVO;
});
六、SimpleQuery工具类
Simplequery可以对selectList查询后的结果用stream流
进行了一些封装,使其可以返回一些指定结果,简洁api的调用。并且这种方式不需要调用service以及mapper中的接口方法,就可以返回数据查询的结果,极大的简化了开发效率,也简化了代码,并且也对数据的结果做出了一定的封装。
这个工具类常用的情况有三种:
1、处理列表,也就是当返回结果是一个列表时,可以对该列表直接进行一些 Stream 流的操作。例如:
// 封装某一列数据 通过SimpleQuery.list()来拿到某一个数据的集合 我这里是查询年龄为18的全部数据
// 将姓名为张三的数据全部查询出来 并根据id来封装
List<Long> list = SimpleQuery.list(new LambdaQueryWrapper<User>().eq(User::getName, "张三"), User::getId);
for (Long aLong : list) {
System.out.println("姓名为张三的id编号: --> " + aLong);
}
// 可以简写
SimpleQuery.list(new LambdaQueryWrapper<User>().eq(User::getName, "张三"), Student::getId).forEach(System.out::println);
//也可以先查出来,然后再对列表操作
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().eq(User::getName, "张三");
List<User> list1 = userService.list(wrapper);
List<Long> list = SimpleQuery.list2List(list1, User::getId);
for (Long aLong : list) {
System.out.println("姓名为张三的id编号: --> " + aLong);
}
使用消费性接口处理列表:
/**
* 代码意思 --> 不设置条件查询内容 那么就查询所有的数据 将查询出来的所有的数据
* 根据姓名封装筛选出来 那么查询结果只返回姓名
* 在根据消费性接口 进行处理
* 以下可以简写成Lambda表达式的形式
*/
List<String> list = SimpleQuery.list(new LambdaQueryWrapper<User>(), User::getName, new Consumer<User>() {
@Override
public void accept(User user) {
/**
* 调用Optional.of返回所有的name属性 在将name的值通过map()方法里面的toUpperCase全部替换成大写
* 然后在通过ifPresent(student::setName)将参数设置为大写
*/
Optional.of(user.getName()).map(String::toUpperCase).ifPresent(user::setName);
}
});
list.forEach(System.out::println);
2、将返回列表 处理成 map 集合:
/**
* map重载的许多 我只解释其中一种
* 第一个参数条件对象
* 第二个参数筛选的key值
* 第三个参数筛选的value值
* 第四个参数是消费性接口
* public static <E, A, P> Map<A, P> map(LambdaQueryWrapper<E> wrapper, SFunction<E, A> keyFunc, SFunction<E, P> valueFunc, Consumer<E>... peeks) {
* return list2Map(Db.list(wrapper.setEntityClass(getType(keyFunc))), keyFunc, valueFunc, peeks);
* }
*/
// 条件查询未设置 查询所有条件
// 在所有条件中
// 我将 key设置为 id 将value设置name 那么返回的结果就是以 key、value的形式进行封装
// 查询结果 :aLong == 》 1 s == 》xiaohong
// aLong == 》 2 s == 》xaioming
// aLong == 》 3 s == 》xiaohu
// aLong == 》 5 s == 》Model测试数据1
Map<Long, String> map = SimpleQuery.map(new LambdaQueryWrapper<User>(), User::getId, User::getName);
map.forEach((aLong, s) -> System.out.println("aLong == 》 " + aLong+ " s == 》" + s));
/**
* 第一个参数条件对象
* 第二个参数根据结果筛选
* 第三个参数消费性接口
* public static <E, A> Map<A, E> keyMap(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, Consumer<E>... peeks) {
* return list2Map(Db.list(wrapper.setEntityClass(getType(sFunction))), sFunction, Function.identity(), peeks);
* }
*/
//将name设置为key 返回的结果就是以name当作key键
//返回结果 -- 》 {xiaohong=Student(id=1, name=xiaohong, age=18, gender=0), xaioming=Student(id=2, name=xaioming, age=18, gender=0), Model测试数据1=Student(id=5, name=Model测试数据1, age=18, gender=0), xiaohu=Student(id=3, name=xiaohu, age=20, gender=0)}
Map<String, User> map = SimpleQuery.keyMap(new LambdaQueryWrapper<User>(), User::getName);
System.out.println(map);
map.forEach((aLong, s) -> System.out.println("aLong == 》 " + aLong+ " s == 》" + s));
3、将结果根据条件进行分组
/**
* group重载的许多 我只解释其中一种
* 第一个参数是条件对象
* 第二个参数是对条件查询对象查询出来的值进行筛选
* 第三个参数消费性接口
* public static <E, A> Map<A, List<E>> group(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, Consumer<E>... peeks) {
* return listGroupBy(Db.list(wrapper.setEntityClass(getType(sFunction))), sFunction, peeks);
* }
*/
//分组查询 我未设置具体的查询条件 直接返回所有的数据
//并且我根据姓名进行分组 那么就会把相同的姓名作为一组进行划分
// name == 姓名
// user == User对象
// 查询结果:name == > 李四 user == >[User(super=com.test.demo.model.User@1292f814, id=2, name=李四, job=程序员, salary=2800.0, status=null, createTime=2024-08-19T17:15:34, updateTime=2024-08-19T17:15:34, deleted=0)]
//name == > 张三 user == >[User(super=com.test.demo.model.User@662cf045, id=1, name=张三, job=随便, salary=2000.0, status=null, createTime=2024-08-19T17:15:34, updateTime=2024-08-19T17:15:34, deleted=0)]
Map<String, List<User>> group = SimpleQuery.group(new LambdaQueryWrapper<User>(), User::getName);
group.forEach((name, user) -> System.out.println("name == > " + name + " user == >" + user));
以上就是基本的使用,总而言之,就是使用SimpleQuery工具类可以让开发更加的快,并且代码美观,简洁。
七、ActiveRecord
ActiveRecord(活动记录,简称AR),是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。ActiveRecord 一直广受解释型动态语言( PHP 、 Ruby 等)的喜爱,通过围绕一个数据对象进行CRUD操作。而 Java 作为准静态(编译型)语言,对于 ActiveRecord 往往只能感叹其优雅,所以 MP 也在 AR 道路上进行了一定的探索,仅仅需要让实体类继承 Model 类且实现主键指定方法,即可开启 AR 之旅。
其实简单来说就是可以直接使用 实体类对象进项增删改查等操作。
步骤 一:让实体类继承Model类
@Getter
@Setter
@ToString(callSuper = true)
@Accessors(chain = true)
@TableName("user")
public class User extends Model<User> {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
private String name;
private String job;
private Double salary;
private OrderState status;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableLogic
private Long deleted;
}
步骤二:就可以直接使用实体类对象调用这些增删改查方法了,简化了操作的语法,但是他的底层依然是需要UserMapper的,所以持久层接口并不能省略【2】测试ActiveRecord模式的增删改查添加数据
User user = new User();
user.setName("测试a");
user.insert();
Model抽象类方法:
注意: 必须存在对应的原始mapper并继承baseMapper并且可以使用
八、自定义ID生成器
自 3.3.0 开始,默认使用雪花算法+UUID
(不含中划线),一般我们也会重写生成id的方法。
原本雪花算法的生成规则:
雪花算法是一个分布式id生成算法,它生成的id一般情况下具有唯一性。由64位01数字组成,第一位是符号位,始终为0。接下来的41位是时间戳字段,根据当前时间生成。然后中间的10位表示机房id+机器id,也可以是单独的机器id。最后12位是序列号。
我们一般重写也就是使用模仿雪花算法的规则进行改写,一般不改也可以正常使用。
如果自定义的话,可以通过实现IdentifierGenerator
接口来创建自定义的ID生成器。以下是一个简单的自定义ID生成器的例子,使用雪花算法(Snowflake)生成ID。
首先,创建自定义的ID生成器类:
@Slf4j
@Primary
public class yourIdentifierGenerator implements IdentifierGenerator {
@Override
public synchronized Number nextId(Object entity) {
return ;
}
}
重写nextId()方法,就是我们自己的id生成策略,我们可以借鉴一下mybatisPlus原始的方法:
public class Sequence {
private static final Log logger = LogFactory.getLog(Sequence.class);
private final long twepoch = 1288834974657L;
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
private final long maxWorkerId = 31L;
private final long maxDatacenterId = 31L;
private final long sequenceBits = 12L;
private final long workerIdShift = 12L;
private final long datacenterIdShift = 17L;
private final long timestampLeftShift = 22L;
private final long sequenceMask = 4095L;
private final long workerId;
private final long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
private InetAddress inetAddress;
public Sequence(InetAddress inetAddress) {
this.inetAddress = inetAddress;
this.datacenterId = this.getDatacenterId(31L);
this.workerId = this.getMaxWorkerId(this.datacenterId, 31L);
}
public Sequence(long workerId, long datacenterId) {
Assert.isFalse(workerId > 31L || workerId < 0L, String.format("worker Id can't be greater than %d or less than 0", 31L), new Object[0]);
Assert.isFalse(datacenterId > 31L || datacenterId < 0L, String.format("datacenter Id can't be greater than %d or less than 0", 31L), new Object[0]);
this.workerId = workerId;
this.datacenterId = datacenterId;
}
protected long getMaxWorkerId(long datacenterId, long maxWorkerId) {
StringBuilder mpid = new StringBuilder();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (StringUtils.isNotBlank(name)) {
mpid.append(name.split("@")[0]);
}
return (long)(mpid.toString().hashCode() & '\uffff') % (maxWorkerId + 1L);
}
protected long getDatacenterId(long maxDatacenterId) {
long id = 0L;
try {
if (null == this.inetAddress) {
this.inetAddress = InetAddress.getLocalHost();
}
NetworkInterface network = NetworkInterface.getByInetAddress(this.inetAddress);
if (null == network) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
if (null != mac) {
id = (255L & (long)mac[mac.length - 2] | 65280L & (long)mac[mac.length - 1] << 8) >> 6;
id %= maxDatacenterId + 1L;
}
}
} catch (Exception var7) {
logger.warn(" getDatacenterId: " + var7.getMessage());
}
return id;
}
public synchronized long nextId() {
long timestamp = this.timeGen();
if (timestamp < this.lastTimestamp) {
long offset = this.lastTimestamp - timestamp;
if (offset > 5L) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
}
try {
this.wait(offset << 1);
timestamp = this.timeGen();
if (timestamp < this.lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
}
} catch (Exception var6) {
throw new RuntimeException(var6);
}
}
if (this.lastTimestamp == timestamp) {
this.sequence = this.sequence + 1L & 4095L;
if (this.sequence == 0L) {
timestamp = this.tilNextMillis(this.lastTimestamp);
}
} else {
this.sequence = ThreadLocalRandom.current().nextLong(1L, 3L);
}
this.lastTimestamp = timestamp;
return timestamp - 1288834974657L << 22 | this.datacenterId << 17 | this.workerId << 12 | this.sequence;
}
protected long tilNextMillis(long lastTimestamp) {
long timestamp;
for(timestamp = this.timeGen(); timestamp <= lastTimestamp; timestamp = this.timeGen()) {
}
return timestamp;
}
protected long timeGen() {
return SystemClock.now();
}
}
九、修改批量保存为自定义批量插入
在mybatisPlus中其实批量保存时并不是真正的保存,而是循环保存得。可以看一下保存200000条数据的耗时:
List<User> userList = new ArrayList<>();
for(int i = 0; i < 199999; i++){
User user = new User();
user.setName("张三");
user.setJob("工作");
user.setSalary(200000D);
userList.add(user);
}
long s = System.currentTimeMillis();
userService.saveBatch(userList);
System.out.println("保存200000条数据消耗" + (System.currentTimeMillis() - s) + "ms");
1、添加数据库连接配置
还是比较慢的,mybatisPlus为我们提供了一种优化方式,就是在连接url上配置批量操作的属性,如下:
jdbc:mysql://数据库地址/数据库名?useUnicode=true&characterEncoding=UTF8&allowMultiQueries=true&rewriteBatchedStatements=true
就是在末尾加上 &rewriteBatchedStatements=true 这个配置,加上之后,可以再试一下批量保存200000条数据:
可以发现时间将近缩短了一半,这只是其中一种方式,我们也可以通过自定义批量保存的方式来进行优化。
2、自定义批量保存
分为四步:
1、首先我们自定义一个RootMapper, 继承BaseMapper,自定义自己的批量插入或者更新方法,如下:
public interface RootMapper<T> extends BaseMapper<T> {
/**
* 批量新增
* @param batchList
* @return
*/
int insertBatch(@Param("list") Collection<T> batchList);
/**
* 批量跟新
* @param batchList
* @return
*/
int updateBatch(@Param("list")Collection<T> batchList);
}
2、定义InsertBatchColumn 继承 AbstractMethod ,下面基本就是一些通用的写法,不同的Mybatis-plus有一点点区别,本文用的版本为3.5.3.1版本,代码如下:
@Slf4j
public class InsertBatchColumn extends AbstractMethod {
@Setter
@Accessors(chain = true)
private Predicate<TableFieldInfo> predicate;
public InsertBatchColumn() {
super("insertBatch");
}
public InsertBatchColumn(Predicate<TableFieldInfo> predicate) {
// 此处的名称必须与后续的RootMapper的新增方法名称一致
super("insertBatch");
this.predicate = predicate;
}
@SuppressWarnings("Duplicates")
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
List<TableFieldInfo> fieldList = tableInfo.getFieldList();
String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(true,false) +
this.filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY);
String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + RIGHT_BRACKET;
String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(true,ENTITY_DOT, false) +
this.filterTableFieldInfo(fieldList, predicate, i -> i.getInsertSqlProperty(ENTITY_DOT), EMPTY);
insertSqlProperty = LEFT_BRACKET + insertSqlProperty.substring(0, insertSqlProperty.length() - 1) + RIGHT_BRACKET;
String valuesScript = SqlScriptUtils.convertForeach(insertSqlProperty, "list", null, ENTITY, COMMA);
String keyProperty = null;
String keyColumn = null;
// 表包含主键处理逻辑,如果不包含主键当普通字段处理
if (tableInfo.havePK()) {
if (tableInfo.getIdType() == IdType.AUTO) {
/* 自增主键 */
keyGenerator = Jdbc3KeyGenerator.INSTANCE;
keyProperty = tableInfo.getKeyProperty();
keyColumn = tableInfo.getKeyColumn();
} else {
if (null != tableInfo.getKeySequence()) {
keyGenerator = TableInfoHelper.genKeyGenerator(modelClass.getName(), tableInfo, builderAssistant);
keyProperty = tableInfo.getKeyProperty();
keyColumn = tableInfo.getKeyColumn();
}
}
}
String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
// 注意第三个参数,需要与后续的RootMapper里面新增方法名称要一致,不然会报无法绑定异常
return this.addInsertMappedStatement(mapperClass, modelClass, "insertBatch", sqlSource, keyGenerator, keyProperty, keyColumn);
}
}
定义 UpdateBatchColumn 继承 AbstractMethod ,代码如下:
public class UpdateBatchColumn extends AbstractMethod {
public UpdateBatchColumn(String methodName) {
super(methodName);
}
@SuppressWarnings("Duplicates")
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
String sql = "<script>\n<foreach collection=\"list\" item=\"item\" separator=\";\">\nupdate %s %s where %s=#{%s} %s\n</foreach>\n</script>";
String additional = tableInfo.isWithVersion() ? tableInfo.getVersionFieldInfo().getVersionOli("item", "item.") : "" + tableInfo.getLogicDeleteSql(true, true);
String setSql = sqlSet(tableInfo.isWithLogicDelete(), false, tableInfo, false, "item", "item.");
String sqlResult = String.format(sql, tableInfo.getTableName(), setSql, tableInfo.getKeyColumn(), "item." + tableInfo.getKeyProperty(), additional);
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass);
// 第三个参数必须和RootMapper的自定义方法名一致
return this.addUpdateMappedStatement(mapperClass, modelClass, "updateBatch", sqlSource);
}
3、自定义sql注入,MysqlInjector继承DefaultSqlInjector ,代码如下:
public class MysqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
List<AbstractMethod> methods = super.getMethodList(mapperClass,tableInfo);
// 自定义的insert SQL注入器
methods.add(new InsertBatchColumn());
// // 自定义的update SQL注入器,参数需要与RootMapper的批量update名称一致
methods.add(new UpdateBatchColumn("updateBatch"));
return methods;
}
}
4、定义MybatiesPlus的配置文件,将 MysqlInjector 注入进去,代码如下
@Configuration
public class MybatiesPlusConfig {
@Bean
public MysqlInjector sqlInjector(){
return new MysqlInjector();
}
}
5、再次执行200000条数据:
List<User> userList = new ArrayList<>();
for(int i = 0; i < 199999; i++){
User user = new User();
user.setName("张三");
user.setJob("工作");
user.setSalary(200000D);
userList.add(user);
}
long s = System.currentTimeMillis();
userService.saveOrUpdateBatch(userList);
System.out.println("保存200000条数据消耗" + (System.currentTimeMillis() - s) + "ms");
比只改数据库配置还要再快一些。
Mybaties-Plus saveBatch()、自定义批量插入、多线程批量插入性能测试和对比_mybatisplus savebatch-CSDN博客