目录
问题
最近学校课程项目遇到了一个问题,有关于jpa的save方法
①jpa 会 把database 中非null的字段覆盖,比如,User表,有id, name, password,某一次你更新了name,但是password没改,save完后,db中相应的password会被覆盖成null,动到了数据库的数据,这是我们忍不了的
②在更新某数据的时候,jpa默认先select,然后update,这是一个非常鸡肋的设计,当访问量大时,这影响着性能。
先看一下SimpleJpaRepository的源码
@Transactional public <S extends T> S save(S entity) { Assert.notNull(entity, "Entity must not be null."); if (this.entityInformation.isNew(entity)) { this.em.persist(entity); return entity; } else { return this.em.merge(entity); } }
简单解释一下,当传入一个实体,首先判断是否存在id,
this.entityInformation.isNew(entity)
如果不存在则执行persist,你可以理解成插入(insert),否则执行merge,但是merge会select然后判断是否相同再update
网上找了很久,基本上都是告诉你,先找出对象,变更,再存。
不错,是个办法,但是不好
尝试解决
@dynamicUpdate似乎可以解决第一个问题,这里我不做讨论了
我们的目标是两个问题都解决了,不用select就update并且不能覆盖null值,就像mybatis一样
有关merge会select那没办法了,只能重写save方法
@Transactional
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
Session session = em.unwrap(Session.class);
session.update(entity);
}
}这样就不会select了,但是第二个问题怎么办?
后面想到了@Column(updatable = false)会在数据更新的时候跳过该字段
那我们每次把null值的字段加上这个注解不就好了?
动态加上@Column注解
对于怎么加上@Column(updatable = false),利用java的反射,我这里就不贴代码了,网上很多。
你会发现加上之后仍然没用,后面猜想是不是 .class 文件没改的问题
改一下 .class文件
ClassPool aDefault = ClassPool.getDefault();
addAnnotation(className,attributeName,typeName);
//使用类的全类名
CtClass ctClass = aDefault.get(className);
//获取当前项目系统路径
File file = new File(".");
//找到class文件地址
String canonicalPath = file.getCanonicalPath() + "target所在地址";
System.out.println(canonicalPath);
byte[] bytes = ctClass.toBytecode();
//写入到文件夹 --->这样就可以修改编译之后的class文件了
FileOutputStream fileOutputStream = new FileOutputStream(canonicalPath);
fileOutputStream.write(bytes);
fileOutputStream.close();

嗯,成功了
可
还是
没用!!!
只有第一次有用,后面再改也不会变
我猜测可能是初始化的时候jpa已经固定下来了,做不到动态更新。。。
后面又想通过刷新bean容器使jpa改变,但是。。。我不会!
希望有大佬能提点意见
解决
转念一想,既然不想select就update那为毛不直接手写update Sql语句啊?
说干就干
对于一个待存对象
我们把它所有的字段遍历一遍,如果为null就不更新,否则加入update SQL语句中
见代码
package cn.edu.xmu.javaee.core.jpa;
import org.apache.ibatis.javassist.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.Table;
import java.lang.reflect.Field;
import java.util.Arrays;
public class UpdateHelper {
private static Logger logger = LoggerFactory.getLogger(UpdateHelper.class);
/**
* 获取update的sql语句
* @author jslsb
* @param bo
* @return
* @throws NotFoundException
* @throws IllegalAccessException
*/
public static String getSql(Object bo) throws NotFoundException, IllegalAccessException {
Class<?> target = bo.getClass();
StringBuffer stringBuffer = new StringBuffer("update ");
logger.debug("getSql : className = {}",target.getName());
//获取表名
String table = target.getAnnotation(Table.class).name();
logger.debug("getSql : table = {}",table);
stringBuffer.append(table).append(" set ");
//获取所有属性
Field[] field = target.getDeclaredFields();
//id列
StringBuffer id = new StringBuffer("");
//判断属性是否为null
Arrays.stream(field).distinct().forEach(pp->{
pp.setAccessible(true);
boolean Is_String = pp.getType().getSimpleName().equals("String")||pp.getType().getSimpleName().equals("LocalDateTime");
Object object = getValueByPropertyName(bo, pp.getName());
logger.debug("getSql : pp = {}, object = {}",pp,object);
String column = getColumn(pp.getName());
if(column.equals("id"))id.append(object);
logger.debug("getSql : columns = {}",column);
if(null != object && !column.equals("id")){
stringBuffer.append(column).append(" = ");
StringBuffer s = new StringBuffer();
if(Is_String)s.append("'").append(object.toString()).append("'").append(", ");
else s.append(object.toString()).append(", ");
stringBuffer.append(s.toString());
}
});
stringBuffer.deleteCharAt(stringBuffer.length()-2).append("where id = ").append(id.toString()).append(";");
logger.info("getSql : sql = {}",stringBuffer.toString());
return stringBuffer.toString();
}
/**
* 获取列
* @param name
* @return
*/
public static String getColumn(String name){
StringBuffer stringBuffer = new StringBuffer("");
for(int i = 0; i<name.length(); i++){
char ch = name.charAt(i);
if(Character.isUpperCase(ch))stringBuffer.append('_');
stringBuffer.append(Character.toLowerCase(ch));
}
return stringBuffer.toString();
}
/**
* 获取值
* @param obj
* @param propertyName
* @return
*/
public static Object getValueByPropertyName(Object obj, String propertyName) {
StringBuffer stringBuffer = new StringBuffer("get");
stringBuffer.append(Character.toUpperCase(propertyName.charAt(0))).append(propertyName.substring(1));
logger.info("getValueByPropertyName : {} = {}","method",stringBuffer.toString());
try {
return obj.getClass().getMethod(stringBuffer.toString()).invoke(obj);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
save方法重写
package cn.edu.xmu.javaee.core.jpa;
import cn.edu.xmu.javaee.core.jpa.UpdateHelper;
import org.apache.ibatis.javassist.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import javax.persistence.EntityManager;
import javax.persistence.Query;
/**
* 重写save方法
* @param <T>
* @param <ID>
*/
public class SimpleJpaRepositoryImpl<T, ID> extends SimpleJpaRepository<T, ID> {
private final JpaEntityInformation<T, ?> entityInformation;
private final EntityManager em;
@Autowired
public SimpleJpaRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityInformation = entityInformation;
this.em = entityManager;
}
/**
* 通用save方法 :新增/选择性更新
*/
@Override
@Transactional
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (entityInformation.isNew(entity)) {
em.persist(entity);
} else {
String sql = null;
try {
sql = UpdateHelper.getSql(entity);
} catch (NotFoundException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
Query x = em.createNativeQuery(sql);
x.executeUpdate();
}
return entity;
}
}
最后,记得在启动类更改!!!!!!!!
@EnableJpaRepositories(value = "类的位置", repositoryBaseClass = SimpleJpaRepositoryImpl.class)
public class ShopApplication {
public static void main(String[] args) {
SpringApplication.run(ShopApplication.class, args);
}
}测试
@Test
public void JapTest(){
ShopPo po = new ShopPo();
po.setId(30L);
po.setName("j2slsb");
po.setCreatorName("下次一定");
Common.putGmtFields(po,"modified");
shopPoMapper.save(po);
Optional<ShopPo> newPo = shopPoMapper.findById(30L);
assertThat(newPo.get().getName().equals("j2slsb"));
assertThat(newPo.get().getDeposit().equals(5000000L));
}测试通过

这样我们就实现了不先select 后 update,并且不覆盖null。
当然想法很简单,效率是否高也是一个问题。
大佬如果有什么好的办法,欢迎讨论
创作不易,给个赞叭