Springboot集成JPA,开箱即用
不知为什么在以前的一段时间内,我特别喜欢用 JPA
,它给我印象就是小巧灵便,为我省去了很多不必要的编码,带给我不一样的代码输出效率,因为业务在垂直划分过程中都相对来说封闭,要求在编码过程中相对来说实体关联没有那么复杂,而且项目本身的交付周期特别短,我就选择 JPA
作为我们某个特定项目的专用持久层框架,当然 JPA
自身的优势就不说啦。
项目架构目的就是为了高质量、低成本、更便捷的交付,也是我那段时间里秉承的思路。
下面言归正传,我们用我之前项目在springboot集成 JPA
来做一个演示。
2.1.1. pom文件
pom中引入 spring-boot-starter-data-jpa
依赖,注意版本。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
2.1.2. 自定义Repository工厂类
JPA
默认工厂类 JpaRepositoryFactoryBean
,并不能满足我们实际要求,比如我们想在插入或者修改的适合做些事情,默认工厂类就不会支持,所以我们重写一个自己的,继承 JpaRepositoryFactoryBean
即可。
继承后重写 createRepositoryFactory
方法,指定我们自己的,这里我用一个内静态类来,这个内部类 BaseRepositoryFactory
也需要继承 JpaRepositoryFactory
工厂,此处我们指定自己真实的工厂类实现 BaseRepositoryImpl
。
@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new BaseRepositoryFactory(entityManager);
}
private static class BaseRepositoryFactory<T,I extends Serializable> extends JpaRepositoryFactory{
private final EntityManager em;
public BaseRepositoryFactory(EntityManager em) {
super(em);
this.em = em;
}
@Override
protected JpaRepositoryImplementation<?, ?> getTargetRepository(RepositoryInformation information, EntityManager entityManager) {
return new BaseRepositoryImpl<T, I>((Class<T>) information.getDomainType(), em);
}
/**
* 设置具体的实现类的class
* @param metadata
* @return
*/
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return BaseRepositoryImpl.class;
}
}
下面就可以在我们自己的类做我们的自己事情,同时我们实现类中抽象出来一个接口暴露对外,这个接口定义我们需要共用的方法,具体实现我们额外实现。这样设计有个好处,就是耦合度降低,扩展方便。
类之间关系图- 抽象接口
@NoRepositoryBean
@Transactional(readOnly=true,rollbackFor = Exception.class)
public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
/**
* 根据主键删除
*
* @param ids
*/
void delete(ID[] ids);
/**
*
* @param sql
* @return
*/
List<Object[]> listBySQL(String sql);
public Long getTargetId(String sql);
/**
*
* @param sql
* @param args
*/
@Transactional(rollbackFor = Exception.class)
void updateBySql(String sql,Object...args);
@Transactional(rollbackFor = Exception.class)
void updateByHql(String hql,Object...args);
Page<T> findCriteria(Specification<T> spec, Pageable pageable);
int batchInsert(String sql);
- 实现类
@SuppressWarnings({"unchecked"})
public class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {
//
private final EntityManager entityManager;
public BaseRepositoryImpl(Class<T> domainClass, EntityManager em) {
super(domainClass, em);
this.entityManager=em;
}
@Override
public void delete(ID[] ids) {
}
@Override
public Long getTargetId(String sql) {
Query query = entityManager.createNativeQuery(sql);
return Long.valueOf(query.getSingleResult().toString());
}
@Override
public void updateBySql(String sql, Object... args) {
Query query = entityManager.createNativeQuery(sql);
int i = 0;
for(Object arg:args) {
query.setParameter(++i,arg);
}
query.executeUpdate();
}
@Override
public void updateByHql(String hql, Object... args) {
Query query = entityManager.createQuery(hql);
int i = 0;
for(Object arg:args) {
query.setParameter(++i,arg);
}
query.executeUpdate();
}
@Override
public List<Object[]> listBySQL(String sql) {
return entityManager.createNativeQuery(sql).getResultList();
}
@Override
public int batchInsert(String sql) {
Query query = entityManager.createNativeQuery(sql);
return query.executeUpdate();
}
public Page<T> find(Class rootCls, CriteriaQuery<T> criteria, int pageNo, int pageSize) {
//count
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery criteriaC = builder.createQuery();
Root root = criteriaC.from(rootCls);
criteriaC.select(builder.count(root));
criteriaC.where(criteria.getRestriction());
List<Long> totals = entityManager.createQuery(criteriaC).getResultList();
Long total = 0L;
for (Long element : totals) {
total += element == null ? 0 : element;
}
//content
TypedQuery<T> query = entityManager.createQuery(criteria);
query.setFirstResult((pageNo - 1) * pageSize);
query.setMaxResults(pageSize);
List<T> content = total > query.getFirstResult() ? query.getResultList() : Collections.<T> emptyList();
Sort sort = Sort.by(Sort.Direction.DESC, Constant.DEFAULT_SORT);
Pageable pageable = PageRequest.of(pageNo, pageSize, sort);
Page<T> pageRst = new PageImpl<T>(content, pageable, total);
return pageRst;
}
@Override
public Page<T> findCriteria(Specification<T> spec, Pageable pageable){
return super.findAll(spec,pageable);
}
}
2.1.3. 抽象实体基类
实例基类,必须要实现一个接口 Persistable
,这个接口只定义ID主键。当然我们自己的基类也会定义这个,但是这并不冲突。
@MappedSuperclass
public abstract class AbsEntity<ID extends Serializable> extends AbstractEntity<ID> implements Persistable<ID> {
private static final long serialVersionUID = 1L;
@Override
public abstract ID getId();
/**
* Sets the id of the entity.
* @param id the id to set
*/
public abstract void setId(final ID id);
//......
}
2.1.4. 抽象Service基类
抽象 Service 提供基础业务类的功能。
/** Service基类
* @ClassName BaseService
* @Description
* @author WCNGS@QQ.COM
* @Github <a>https://github.com/rothschil</a>
* @date 20/12/18 11:05
* @Version 1.0.0
*/
public abstract class BaseService<T extends AbsEntity<?>, ID extends Serializable> {
protected JpaRepository<T, ID> jpaRepository;
public BaseService() {}
/** 重要 **/
public abstract void setJpaRepository(JpaRepository<T, ID> jpaRepository);
public boolean retBoolFindByExample(T t){
ExampleMatcher matcher = ExampleMatcher.matching();
List<String> fields = new ArrayList<String>();
Reflections.getField(t,fields);
for (String fld: fields){
matcher.withMatcher(fld,ExampleMatcher.GenericPropertyMatchers.exact());
}
Example<T> example = Example.of(t,matcher);
if(jpaRepository.findAll(example).size()>0){
return true;
}
return false;
}
public boolean retBoolSave(T t ){
try{
this.save(t);
return true;
}catch (RuntimeException re){
return false;
}
}
public List<T> findByProperty(T t,String propertyName, Object value) {
try {
Class<?> cls = t.getClass();
Field[] fields = cls.getDeclaredFields();
Method[] methods = cls.getDeclaredMethods();
for (int i=0;i<fields.length;i++) {
if(fields[i].getName().equals(propertyName)){
String fieldSetName = StringUtils.parSetName(fields[i].getName());
for (Method met : methods) {
if (fieldSetName.equals(met.getName())) {
met.invoke(t,value);
}
}
}
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return findByEntity(t);
}
public boolean retBoolDelete(T t) {
try {
this.delete(t);
return true;
} catch (RuntimeException re) {
return false;
}
}
public List<T> findByEntity(T t) {
ExampleMatcher matcher = ExampleMatcher.matching();
List<String> fields = new ArrayList<String>();
Reflections.getField(t,fields);
for (String fld: fields){
matcher.withMatcher(fld,ExampleMatcher.GenericPropertyMatchers.exact());
}
/* .withMatcher("username", ExampleMatcher.GenericPropertyMatchers.startsWith())
.withMatcher("address" ,ExampleMatcher.GenericPropertyMatchers.contains())
.withIgnorePaths("password");*/
Example<T> example = Example.of(t,matcher);
return jpaRepository.findAll(example);
}
public Page<T> findPageByEntity(int page, int size, T t) {
size=size==0?10:size;
// TODO Auto-generated method stub
Pageable pageable = PageRequest.of(page, size);
ExampleMatcher matcher = ExampleMatcher.matching();
List<String> fields = new ArrayList<String>();
Reflections.getField(t,fields);
for (String fld: fields){
matcher.withMatcher(fld,ExampleMatcher.GenericPropertyMatchers.exact());
}
/* .withMatcher("username", ExampleMatcher.GenericPropertyMatchers.startsWith())
.withMatcher("address" ,ExampleMatcher.GenericPropertyMatchers.contains())
.withIgnorePaths("password");*/
Example<T> example = Example.of(t,matcher);
return jpaRepository.findAll(example,pageable);
}
/**
* 保存单个实体
*
* @param t 实体
* @return 返回保存的实体
*/
public T save(T t) {
return jpaRepository.save(t);
}
public T saveAndFlush(T t) {
t = save(t);
jpaRepository.flush();
return t;
}
/**
* 根据主键删除相应实体
*
* @param id 主键
*/
public void delete(ID id) {
jpaRepository.delete(findOne(id));
}
/**
* 删除实体
*
* @param t 实体
*/
public void delete(T t) {
jpaRepository.delete(t);
}
/**
* 按照主键查询
*
* @param id 主键
* @return 返回id对应的实体
*/
public T findOne(ID id) {
return jpaRepository.getOne(id);
}
/**
* 实体是否存在
* @method exists
* @author WCNGS@QQ.COM
* @version
* @see
* @param id id 主键
* @return boolean 存在 返回true,否则false
* @exception
* @date 2018/7/3 22:08
*/
public boolean exists(ID id) {
return findOne(id)==null?true:false;
}
/**
* 统计实体总数
* @method count
* @author WCNGS@QQ.COM
* @version
* @see
* @param
* @return long
* @exception
* @date 2018/7/3 22:07
*/
public long count() {
return jpaRepository.count();
}
/**
* 查询所有实体
* @method findAll
* @author WCNGS@QQ.COM
* @version
* @see
* @param
* @return java.utils.List<T>
* @exception
* @date 2018/7/3 22:07
*/
public List<T> findAll() {
return jpaRepository.findAll();
}
/**
* 按照顺序查询所有实体
* @method findAll
* @author WCNGS@QQ.COM
* @version
* @see
* @param sort
* @return java.utils.List<T>
* @exception
* @date 2018/7/3 22:06
*/
public List<T> findAll(Sort sort) {
return jpaRepository.findAll(sort);
}
/**
* 分页及排序查询实体
*
* @param pageable 分页及排序数据
* @return
*/
public Page<T> findAll(Pageable pageable) {
return jpaRepository.findAll(pageable);
}
public Page<T> findEntityNoCriteria(Integer page, Integer size) {
Pageable pageable = PageRequest.of(page, size);
return findAll(pageable);
}
}
2.1.5. 如何使用?
2.1.5.1. 继承实体基类
用 lombok
一目了然,对应字段名以及列与属性对应关系,代码整体简洁。
@EqualsAndHashCode(callSuper=false)
@Builder(toBuilder=true)
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name="tb_locations")
public class Location extends AbsEntity<Long> {
@Id
@Column(name = "id", nullable = false)
private Long id;
@Column(name = "flag")
private String flag;
@Column(name = "local_code")
//省略....
}
2.1.5.2. 继承repository基类
直接继承就完事,方法命名的书写,有些讲究,这里就不单独说明,有兴趣童鞋自行恶补。
public interface LocationRepository extends BaseRepository<Location, Long>,JpaSpecificationExecutor<Location> {
List<Location> findByLv(int lv);
//省略....
}
2.1.5.3. 继承Service基类
需要引入我们 locationRepository
,这一步很关键,当然我看有的人将引入的 Repository
弄在类泛型中,效果一样。
@Service(value="locationService")
@Transactional(readOnly = true)
public class LocationService extends BaseService<Location, Long> {
private LocationRepository locationRepository;
@Autowired
@Qualifier("locationRepository")
@Override
public void setJpaRepository(JpaRepository<Location, Long> jpaRepository) {
this.jpaRepository=jpaRepository;
this.locationRepository =(LocationRepository)jpaRepository;
}
public List<Location> getLocationListByLevel(int lv){
return locationRepository.findByLv(lv);
}
}
2.1.5.4. 启动类
因为我们自定义了工厂类,所以我们的启动类需要将我们工厂类引入进来,所以 repositoryFactoryBeanClass
属性需要了解下。
这样子 springboot
整合 JPA
就完啦。
@EnableSwagger2
@EnableJpaAuditing
@SpringBootApplication
@EnableJpaRepositories(basePackages = {"xyz.wongs.drunkard"},
repositoryFactoryBeanClass = BaseRepositoryFactoryBean.class//Specify your own factory class
)
public class MoonApplication {
public static void main(String[] args) {
SpringApplication.run(MoonApplication.class,args);
}
}
EnableJpaRepositories中指定工厂类