小编是从python转到java的,因此小编对python世界中的sqlalchemy和django-orm的牛逼和方便记忆有心。转到java以后,发现java世界里也有类似的工具,只不过说实话,跟python相比,确实有点弱。java中,提供数据库ORM功能的工具叫做JPA。在spring中,专门有一个项目叫做spring-data-jpa用来提供对jpa的支持。我理解jpa只是一个标准,通常使用的jpa的实现是hibernate,这就是为啥默认情况下,当在pom里引入spring-data-jpa的时候,会自动引入hiberate。
废话不多说,我们先来看看spring-data-jpa是如果简化我们的开发的,请看以下代码。这段代码中,我们只需定义一个扩展自JpaRepository的接口,而在该接口中,我们只需要按照spring-data-jpa给定的规则来生成一个函数声明即可。例如该接口中,findById就等于原生的SQL : select * from data_center_info where id = ?
。
public interface DataCenterInfoDao extends JpaRepository<DataCenterInfo, Long>,
JpaSpecificationExecutor<DataCenterInfo> {
/**
* Find by id optional.
*
* @param id the id
* @return the optional
*/
Optional<DataCenterInfo> findById(Long id);
}
一开始感觉这种方式还是挺方便的,但是用着用着发现,这样的方式功能有点不健全的,例如:
- 无法实现join操作
- 只能返回
List<DataCenterInfo>
或者DataCenterInfo
这样的对象,如果我想返回对象中某个字段呢? 瞎了…… - 函数的名字一长,真的让人有点晕乎啊
对于上述的问题,jpa也提供了原生的SQL方式来弥补这样的问题,例如:
public interface DomainRecordHistoryDao extends JpaRepository<DomainRecordHistory, Serializable> {
List<DomainRecordHistory> findByDomainNameAndEnterpriseIdAndCreateTime(String domainName, String enterpriseId, Date date);
@Query(value = "select distinct(create_time) from domain_record_history where domain_name = ?1 and enterprise_id=?2 order by create_time ASC ", nativeQuery = true)
List<Date> findCreateTimeByDomainNameAndEnterpriseId(String domainName, String enterpriseId);
}
但是,既然已经用了ORM的方式干掉SQL了,为啥我要倒退回去重写SQL,我实在不能接受原生的这种SQL写法。功夫不负有心人,翻了不少的书和网页,终于让我找到更好的方式,并且在《spring实战(第四版)》中也有提到,书中将接下来我要提到的这种方式,称之为混合自定义的功能。
具体来说,当spring-data-jpa为Repository接口生产实现的时候,它还会查找名字与接口相同,并且添加了Impl后缀的一个类。如果这个类存在的话,spring-data-jpa将会把它的方法与spring-data-jpa所生成的方法合并在一起。对于上述DataCenterInfoDao
接口而言,要查找的类名就是DataCenterInfoDaoImpl
。
我首先定义了如下的接口:
public interface DataCenterInfoAddition {
List<Tuple> countDataCenterInfoByArea(Province belongProvince);
}
紧接着,我修改一下之前定义的DataCenterInfoDao
:
public interface DataCenterInfoDao extends JpaRepository<DataCenterInfo, Long>,
JpaSpecificationExecutor<DataCenterInfo>, DataCenterInfoAddition {
/**
* Find by id optional.
*
* @param id the id
* @return the optional
*/
Optional<DataCenterInfo> findById(Long id);
}
最后我定义一个DataCenterInfoDaoImpl
实现类,用于实现DataCenterInfoAddition
。在这个实现中,我直接使用JPA Criteria API来实现对应的数据库功能。countDataCenterInfoByArea
中实现的功能无法直接使用jpa定义函数名的方式来实现,这个函数返回值有两个,一个是表的province字段以及它对应的数目。
public class DataCenterInfoDaoImpl implements DataCenterInfoAddition {
@PersistenceContext
private EntityManager em;
@Override
public List<Tuple> countDataCenterInfoByArea(Province belongProvince) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createQuery(Tuple.class);
Root<DataCenterInfo> nnInfo = cq.from(DataCenterInfo.class);
cq.multiselect(nnInfo.get("province"), cb.count(nnInfo)).groupBy(nnInfo.get("province"))
.orderBy(cb.desc(cb.count(nnInfo)));
if (belongProvince != null && !belongProvince.getName()
.equals(ProvinceEnum.Jituan.getName()) && !belongProvince.getName()
.equals(ProvinceEnum.Quanguo.getName())) {
cq.where(cb.equal(nnInfo.get("belongProvince"), belongProvince));
}
return em.createQuery(cq).getResultList();
}