mybatis的批量修改,首先针对修改的场景说一下,该批量修改只能批量修改单表,无法多表连接修改多表。
设计思路:传入参数:List<T> list 当前要批量修改的数据集合 ; Class<T> t T.class对象
1.通过自定义注解,在类名即属性命名上打上对应注解:参数为数据库中的表名以及对应的列名
2.在工具类方法利用反射获取到表名,列名以及类中对应列名的属性名,然后通过传入的list集合中对象的主键ID获取到数据库中存储的对应的对象。
3.将这些对象一一对比,观察哪些属性发生了改变,将这些属性记录下来,然后将需要修改的属性修改。
解释的可能不清楚,直接上代码:
首先是俩自定义注解:主要是为了记录数据库表名与数据库表中属性
package com.bo.annotate;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DbColumn {
/**
* 属性名称
* @return
*/
String name() default "";
/**
* 是否确认修改
*/
boolean canModify() default false;
}
package com.bo.annotate;
import java.lang.annotation.*;
/**
* 此注解操作数据库
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DbTable {
String name() default "";
}
看看这些注解的用法:@DbTable标注在类上,name指向的是数据库中对应表名,@DbColumn是标注在属性上的,name指的是该属性对应表上的列名,canModify指的是该对象是否可以修改,默认false,不可以修改。
package com.bo.pojo;
import com.bo.annotate.DbColumn;
import com.bo.annotate.DbTable;
@DbTable(name = "t_teacher")
public class Teacher {
/**
* 主键ID
*/
private Long id;
/**
* 姓名
*/
@DbColumn(name = "f_name",canModify = true)
private String name;
/**
* 年龄
*/
@DbColumn(name = "f_age",canModify = true)
private Integer age;
/**
* 性别
*/
@DbColumn(name = "f_subject",canModify = false)
private String subject;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
接下来上批量修改的方法代码,
/**
* 实现任意类的批量修改
* @param list 要修改的对象集合
* @param t 修改对象的字节码
*/
public void batchUpdate(List<T> list,Class<T> t) throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
if(CollectionUtils.isEmpty(list)){
throw new RuntimeException("批量修改集合不能为空");
}
//思路:1.通过反射获取list集合中对应的ID,2.通过ID在数据库中查询到对应的数据
//3.对比新旧数据,将改变后的数据改入数据库中
String getId = "getId";
Method method = t.getMethod(getId, null);
if(method == null){
throw new RuntimeException("没有找到对应getId方法");
}
//获取到对应的ID集合
List<Object> idList = new ArrayList<>();
for(T obj: list){
Object invoke = method.invoke(obj, null);
idList.add(invoke);
}
//获取自定义注解的表名,属性以及是否可以修改
DbTable dbTable = t.getAnnotation(DbTable.class);
if(dbTable == null){
throw new RuntimeException("DbTable注解无法获取表名");
}
//获取表名
String tableName = dbTable.name();
Field[] fields = t.getDeclaredFields();
if(fields == null || fields.length == 0){
throw new RuntimeException("未找到要修改的属性");
}
Map<String, String> linkedMap = new LinkedHashMap<>();
for(Field field :fields){
DbColumn dbColumn = field.getAnnotation(DbColumn.class);
if(dbColumn != null){
String columnName = dbColumn.name();
boolean canModify = dbColumn.canModify();
if(!canModify){
continue;
}
//要修改的表列名称
linkedMap.put(field.getName(),columnName);
}
}
//通过表名,表列名称,对应的ID数组查询出对应属性
if(CollectionUtils.isEmpty(linkedMap)){
throw new RuntimeException("未找到要修改的属性");
}
//这里的泛型返回类型出了一些问题,我想要返回一个对象,类名无法通过参数传递,也无法用object来返回,最后,只能使用map返回
List<Map<String,Object>> oldList = dataBaseUtilsMapper.queryUpdateAttribute(tableName,linkedMap,idList,t.getName());
if(CollectionUtils.isEmpty((oldList))){
throw new RuntimeException("要修改的数据不存在");
}
Map<Long, Map<String, Object>> oldMap = new HashMap<>();
for(Map<String,Object> oldObj: oldList){
oldMap.put((Long) oldObj.get("id"),oldObj);
}
//我先考虑一下关于批量修改的设计思想方面 修改的话是将对象要修改的属性给统计到一个map中,key为属性名,value为属性值
List<Map<String, Object>> updateObjs = new ArrayList<>();
for(T newObj: list){
//存放修改后的属性和值
Map<String, Object> updateAttrabutes = new HashMap<>();
Long id = (Long)method.invoke(newObj, null);
//旧据中包含该主键ID
if(oldMap.containsKey(id)){
//获取到的旧对象 String为属性名 Object为属性对应的值 新对象为newObj
Map<String, Object> oldObj = oldMap.get(id);
for(Map.Entry<String, Object> oldAttrbute:oldObj.entrySet()){
//通过反射获取新对象中对应的值,与旧对象开始比较
String key = oldAttrbute.getKey();
Object oldValue = oldAttrbute.getValue();
Object newValue = getMethodValue(key, t, newObj);
//从前后台传递过来的对象有可能是空值(非必填项)
if(!(oldValue != null?oldValue.toString():"").equals(newValue != null?newValue.toString():"")) {
String columnName = linkedMap.get(key);
updateAttrabutes.put(columnName, newValue);
}
}
if(!CollectionUtils.isEmpty(updateAttrabutes)){
updateAttrabutes.put("f_id",id);
updateObjs.add(updateAttrabutes);
}
}
}
if(!CollectionUtils.isEmpty(updateObjs)){
dataBaseUtilsMapper.batchUpdate(tableName,updateObjs);
}
}
/**
* 传入属性名获取属性值的方法
* @param attName 传入的属性名
*@param t 传入的字节码
*@param obj 要调用的实体类对象
* @return 获取到的属性值
*/
private Object getMethodValue(String attName,Class<T> t,T obj) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
StringBuilder builder = new StringBuilder("get");
String beginName = attName.substring(0, 1).toUpperCase();
String endName = attName.substring(1,attName.length());
String getter = builder.append(beginName).append(endName).toString();
Method method = t.getMethod(getter, null);
if(method == null){
throw new RuntimeException("未找到当前属性方法:"+getter);
}
Object invoke = method.invoke(obj, null);
return invoke;
}
queryUpdateAttribute,batchUpdate两个方法对应的sql如下
<update id="batchUpdate">
<foreach collection="updateObjs" item="item" separator=";">
update ${tableName}
<set>
<foreach collection="item" index="key" item="value" separator="," open="" close="">
<if test="key != 'f_id'">
${key} = #{value}
</if>
</foreach>
</set>
where f_id = #{item.f_id}
</foreach>
</update>
<select id="queryUpdateAttribute" resultType="map">
select
f_id as id,
<foreach collection="linkedMap" item="column" separator="," index="attribute">
${column} as #{attribute}
</foreach>
from
${tableName}
where f_id in
<foreach collection="idList" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
对了,记得使用批量修改的时候要改一下项目的配置,因为项目默认是不支持在xml文件中进行批量修改的,所以我们需要加个配置,解决方法:https://blog.csdn.net/mayfla/article/details/78774897
最后就修改成功了,一个针对于所有类的批量修改完成。
接下来就是一个测试类的事:
@Test
public void test03() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, NoSuchFieldException {
List<Teacher> teachers = new ArrayList<>();
Teacher teacher = new Teacher();
teacher.setId(new Long(19));
teacher.setName("嘉德伯爵");
teacher.setAge(22);
teacher.setSubject("计算机");
Teacher teacher1 = new Teacher();
teacher1.setId(new Long(20));
teacher1.setName("月色真美");
teacher1.setAge(21);
teacher1.setSubject("IT");
teachers.add(teacher);
teachers.add(teacher1);
/* log.warn(teacher.getName());*/
utils.batchUpdate(teachers,Teacher.class);
}
现附加数据源详细配置:
spring:
datasource:
# 数据源基本配置
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/student?allowMultiQueries=true
type: com.alibaba.druid.pool.DruidDataSource
# 数据源其他配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
注意:地址一定要这么配置:url: jdbc:mysql://localhost:3306/student?allowMultiQueries=true,allowMultiQueries参数一定要加,不然报错。
楼主亲测,可用,有什么不了解的可以发表评论,很高兴为你解答。