字数:8716
1 起因
- 在springboot的基础之上,引入spring-boot-starter-data-jpa后,简化数据层的开发,甚至不用再写HQL语句;
- 另外,想解决统一的时间戳和逻辑删除的操作
2 问题
惨案发生在这里:
3 具体说明
文件结构如下:
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─xxx
│ │ │ └─qingdu
│ │ │ │ Starter.java #Main方式启动spring
│ │ │ │
│ │ │ ├─bean
│ │ │ │ AbsIdEntity.java #bean的基类,统一的ID/时间戳/假删除字段
│ │ │ │ User.java #案例bean
│ │ │ │
│ │ │ ├─config
│ │ │ │ IdGeneratorConfig.java #全局唯一主键bean
│ │ │ │ JpaConfig.java #spring data jpa的一些配置,包括扫描包、工厂类等
│ │ │ │ RedisConfig.java #redis的template处理bean,跟本例无关
│ │ │ │
│ │ │ ├─controller
│ │ │ │ LibraryCardController.java #证件controller,跟本例无关
│ │ │ │ UserController.java
│ │ │ │
│ │ │ ├─repository
│ │ │ │ │ BaseRepositoryFactoryBean.java #repository实例的工厂
│ │ │ │ │ IBaseRepository.java #repository的基础接口,主要是定义了扩展另外的接口,这样在子接口就不用再定义
│ │ │ │ │ IUserRepository.java #user业务的repository接口,这里面也没什么方法好定义的
│ │ │ │ │
│ │ │ │ └─impl
│ │ │ │ BaseRepositoryImpl.java #repository接口的基础实现,不定义这个实现使用默认的也能操作数据
│ │ │ │
│ │ │ └─service
│ │ │ UserService.java #user服务实现类
│ │ │ UserServiceImpl.java #user服务接口类
│ │ │
│ │ ├─resources
│ │ │ application.properties #spring全局配置
│ │ │ application.yml #spring全局配置
问题在上面都描述过了,再组织一下:
(1)写了base repository interface,没什么内容,如下:
package com.xxx.qingdu.repository;
import java.io.Serializable;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.NoRepositoryBean;
import com.xxx.qingdu.bean.AbsIdEntity;
/**
* DAO基类,继承了另两个类,保证所有Repository都有基本的增删改查以及分页等方法
*
* 所有Repository接口都继承BaseDao
*
* 使用时只需要继承接口,不需要实现类,spring自动通过cglib生成实现类
*
*
* @param <T>
*/
@NoRepositoryBean
public interface IBaseRepository <T extends AbsIdEntity> extends
CrudRepository<T, Serializable>, JpaSpecificationExecutor<T>{
}
(2)写了user repository interface,没什么内容
(3)写了base repository implement,出问题的地方,内容如下:
package com.xx.qingdu.repository.impl;
import java.io.Serializable;
import java.sql.Timestamp;
import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import com.xxx.qingdu.bean.AbsIdEntity;
import com.xxx.qingdu.repository.IBaseRepository;
/**
* impl基类,使其具有Jpa Repository的基本方法
*
* JPA事务的使用:https://www.jianshu.com/p/c84a91992c32
* 加注解实现逻辑删除:https://www.liangzl.com/get-article-detail-3633.html
* @param <T>
*/
//@Transactional
public class BaseRepositoryImpl <T extends AbsIdEntity> extends SimpleJpaRepository<T, Serializable>
implements IBaseRepository<T> {
@SuppressWarnings("unused")
private final EntityManager entityManager;
public BaseRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
this.entityManager = entityManager;
}
// public BaseDaoImpl(JpaEntityInformation<T, Serializable> information, EntityManager entityManager) {
// super(information, entityManager);
// this.entityManager = entityManager;
// }
@Override
public <S extends T> S save(S entity) {
entity.setLastTime(new Timestamp(System.currentTimeMillis()));
S s = super.save(entity); //save定义的是事务,内部先判断有无,有则更新无则插入
return s;
}
/**
* 只做逻辑删除
* 或逻辑删除用注解定义到bean上
*/
@Override
public void delete(T entity) {
entity.setIsDelete(1);
this.save(entity);
}
@Transactional //声明了事务,才能Save,findOne是一个只读事务,另外,最好将注解定义到Service方法上
@Override
public void delete(Serializable id) {
T entity = findOne(id);
entity.setIsDelete(1);
this.save(entity);
}
}
(4)写了bean factory,内容简单,如下:
package com.xx.qingdu.repository;
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import com.xxx.qingdu.bean.AbsIdEntity;
import com.xxx.qingdu.repository.impl.BaseRepositoryImpl;
/**
* https://yq.aliyun.com/articles/465404
* 另一种做法直接改spring data的代码,包括查询排除掉逻辑删除的内容 https://blog.csdn.net/Gavid0124/article/details/79063999
*
* 代替默认的RepositoryFactoryBean,负责返回一个Factory,spring data jpa使用其来创建repository的具体实现
* @param <R> repository的类型
* @param <T> the domain type the repository manages
*/
public class BaseRepositoryFactoryBean<R extends JpaRepository<T, Serializable>, T extends AbsIdEntity>
extends JpaRepositoryFactoryBean<R, T, Serializable> {
public BaseRepositoryFactoryBean(Class<? extends R> repositoryInterface) {
super(repositoryInterface);
}
@Override
protected RepositoryFactorySupport createRepositoryFactory(final EntityManager entityManager) {
return new JpaRepositoryFactory(entityManager) {
protected SimpleJpaRepository<T, Serializable> getTargetRepository(
RepositoryInformation information, EntityManager entityManager) {
Class<T> domainClass = (Class<T>) information.getDomainType(); //获得repository接口关联的Bean类
//根据bean类是否继承了基类,来返回不同的Repository
if(AbsIdEntity.class.isAssignableFrom(domainClass)) {
return new BaseRepositoryImpl(domainClass, entityManager); //这些repository的实现在springboot启动时即初始化
} else {
return new SimpleJpaRepository(domainClass, entityManager);
}
}
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return metadata.getDomainType().isAssignableFrom(AbsIdEntity.class) ?
BaseRepositoryImpl.class : SimpleJpaRepository.class;
}
};
}
}
(5)写了user service interface,就定义几个方法
(6)写了user service implement,出问题的地方,内容如下:
package com.xxx.qingdu.service;
import java.sql.Timestamp;
import java.util.List;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import com.xxx.qingdu.bean.User;
import com.xxx.qingdu.repository.IUserRepository;
import com.xxx.tools.ObjectUtil;
import com.xxx.tools.SnowflakeIdWorker;
@Service
@EnableCaching //启用缓存
public class UserServiceImpl implements UserService{
@Autowired private SnowflakeIdWorker idGenerator;
@Autowired private IUserRepository userRepository;
/**
* 更新用户信息
*
* https://blog.csdn.net/Soulmate_Min/article/details/82777293
*/
@Override
@Transactional //JPA更新需要使用事务机制,即加锁同步,否则update无效
public User updateUser(User u) {
//这里调用的是org.springframework.data.jpa.repository.support.SimpleJpaRepository<T, Serializable>
//而该方法的事务沿用的是注解在类上的readOnly属性?再使用com.xxx.qingdu.repository.impl.BaseRepositoryImpl.save无效。在方法上注解即可
User u_exists = userRepository.findOne(u.getId());
if (u_exists != null) {
// 仅将参数对象里有的内容覆盖到库里的对象
ObjectUtil.combineObject(User.class, u, u_exists);
} else {
u.setCreateTime(new Timestamp(System.currentTimeMillis()));
u_exists = u;
}
return userRepository.save(u_exists);
}
/**
* 首次创建用户信息
*/
@Override
public User createUser(User u) {
//u.setId(idGenerator.nextId());
u.setCreateTime(new Timestamp(System.currentTimeMillis()));
return userRepository.save(u); //调用com.xxx.qingdu.repository.impl.BaseRepositoryImpl.save(S)
}
//@Transactional
@Override
public void deleteUser(Long id_user) {
userRepository.delete(id_user);
}
}
4 结论
-
jpa的更新操作需要注解为事务
-
注解不要写在接口方法上
-
注解可以定义到repository实现类的类上,或方法上,方法上优先
-
提倡注解定义到service的方法上,调用者注解优先
-
还遇到过一个比较匪夷所思的问题,但是同一个@Transactional方法里update操作有效,create操作无效。 改成后者解决问题 ——>
问题的根源在于,顶层Service方法上必须要注解@Transactional
以上内容,在《spring data jpa从入门到精通》里也说明的不全面啊,真可谓不踩坑不知坑的深浅,另外,读书的方法是不是这样,先找到问题,先去尝试解决问题才能求得甚解。