记录一次性 操作百万数据 从数据库拷贝修改再插入报错问题
记录一次性 操作百万数据 从数据库拷贝修改再插入报错问题
问题一: identifier of an instance of was altered from 1353655 to null
由于在同一方法上加入@Transactional(rollbackFor = Exception.class)事务管理导致 实体类在修改主键会报错,
其实我们只有在保存数据的时候才会用到事务(当然这里从数据库查询认为不会报错);
所以从这方面出发,我们可以把事务管理只加载 保存 的地方。
这里提供一种方法(手动抛出异常: TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()):
try {
demodao.batchInsert(demoList);
} catch (Exception e) {
e.printStackTrace();
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
当然如果你数据量小,你可以直接创建一个新的对象,然后把属性赋值给新对象、如BeanUtils.copyProperties等方法。但是如果你数据量大,如果你直接BeanUtils.copyProperties可能会报错:javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session :,这里可以看问题二
问题二:javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session :
hibernate是不允许两个主键相同的对象在同一个session里面的,所以最根本的解决办法就是,当你需要修改这个对象的时候只要修改A的相应的值然后save保存就可以了,没必要再创建B再去保存B.
这里数据量小没有问题,你也可以把数据分段再进行操作,我这里的实体id使用的是自增,另外在使用jpa操作大量数据的时候,修改和插入操作是循环调用save方法插入,大量数据量,效率会有点低。所以我们可以自己实现批量插入,在你的dao层创建BaseRepository接口,并实现。以后其余的接口继承改接口就可以了
如下:
@NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
/**
* 批量增加
* @param entities 批量增加实体
* @param <S> 实体
*/
<S extends T> Iterable<S> batchInsert(Iterable<S> entities);
/**
* 批量更新
* @param entities 批量更新的实体
* @param <S> 实体
*/
<S extends T> Iterable<S> batchUpdate(Iterable<S> entities);
}
实现:
@SuppressWarnings("SpringJavaConstructorAutowiringInspection")
public class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {
/**
* batch大小
*/
private static final int BATCH_SIZE = 500;
/**
* EntityManager
*/
private EntityManager entityManager;
public BaseRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager em) {
super(entityInformation, em);
this.entityManager = em;
}
public BaseRepositoryImpl(Class<T> domainClass, EntityManager em) {
super(domainClass, em);
this.entityManager = em;
}
@Override
@Transactional(rollbackFor = Exception.class)
public <S extends T> Iterable<S> batchInsert(Iterable<S> entities) {
Iterator<S> iterator = entities.iterator();
int index = 0;
while (iterator.hasNext()){
entityManager.persist(iterator.next());
index++;
if (index % BATCH_SIZE == 0){
entityManager.flush();
entityManager.clear();
}
}
if (index % BATCH_SIZE != 0){
entityManager.flush();
entityManager.clear();
}
return entities;
}
@Override
@Transactional(rollbackFor = Exception.class)
public <S extends T> Iterable<S> batchUpdate(Iterable<S> entities) {
Iterator<S> iterator = entities.iterator();
int index = 0;
while (iterator.hasNext()){
entityManager.merge(iterator.next());
index++;
if (index % BATCH_SIZE == 0){
entityManager.flush();
entityManager.clear();
}
}
if (index % BATCH_SIZE != 0){
entityManager.flush();
entityManager.clear();
}
return entities;
}
}
dao层 : public interface XXXDao extends BaseRepository<XXX,Long> 这样,最后记得在启动类上扫描这个实现类,不然会报找不到实现接口的错误
@EnableJpaRepositories(repositoryBaseClass = BaseRepositoryImpl.class)
779

被折叠的 条评论
为什么被折叠?



