本篇对Spring-data-jpa简单的介绍。整合 spring-data 、hibernate
大致整理一个提纲:
<!-- spring版本号 --> <spring.version>5.0.0.RELEASE</spring.version> <!-- hibernate 版本号 --> <hibernate.version>5.2.11.Final</hibernate.version> <spring-data-jpa.version>2.0.0.RELEASE</spring-data-jpa.version> <spring-data-commons.version>2.0.0.RELEASE</spring-data-commons.version>
资源下载 http://download.csdn.net/download/qq_27384769/10134290
Spring-data-jpa的基本介绍:JPA诞生的缘由是为了整合第三方ORM框架,建立一种标准的方式,为了实现ORM的天下归一,目前也是在按照这个方向发展,但是还没能完全实现。在ORM框架中,Hibernate是一支很大的部队,使用很广泛,也很方便,能力也很强,同时Hibernate也是和JPA整合的比较良好,我们可以认为JPA是标准,事实上也是,JPA几乎都是接口,实现都是Hibernate在做,宏观上面看,在JPA的统一之下Hibernate很良好的运行。
Spring-data-jpa 主要是体现在和第三方工具的整合上。而在与第三方整合,于是就有了Spring-data-**这一系列包。包括,Spring-data-jpa,Spring-data-template,Spring-data-mongodb,Spring-data-redis
在使用持久化工具的时候,一般都有一个对象来操作数据库,在原生的Hibernate中叫做Session,在JPA中叫做EntityManager,在MyBatis中叫做SqlSession,通过这个对象来操作数据库。我们一般按照三层结构来看的话,Service层做业务逻辑处理,Dao层和数据库打交道,在Dao中,就存在着上面的对象。那么ORM框架本身提供的功能有什么呢?答案是基本的CRUD,所有的基础CRUD框架都提供,我们使用起来感觉很方便,很给力,业务逻辑层面的处理ORM是没有提供的,如果使用原生的框架,业务逻辑代码我们一般会自定义,会自己去写SQL语句,然后执行。在这个时候,Spring-data-jpa的威力就体现出来了,ORM提供的能力他都提供,ORM框架没有提供的业务逻辑功能Spring-data-jpa也提供,全方位的解决用户的需求。
使用Spring-data-jpa进行开发的过程中,常用的功能,我们几乎不需要写一条sql语句,至少在我看来,企业级应用基本上可以不用写任何一条sql,当然spring-data-jpa也提供自己写sql的方式
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.1.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd" default-lazy-init="true"> <!-- 启动组件扫描,排除@Controller组件,该组件由SpringMVC配置文件扫描 --> <context:component-scan base-package="com.rjsoft"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 属性文件位置 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 数据源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.210.207:3306/demoone?useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> <property name="filters" value="stat"/> <property name="maxActive" value="20"/> <property name="initialSize" value="1"/> <property name="maxWait" value="60000"/> <property name="minIdle" value="1"/> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <property name="minEvictableIdleTimeMillis" value="300000"/> <property name="validationQuery" value="SELECT 'x'"/> <property name="testWhileIdle" value="true"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <!-- 超过时间限制是否回收 --> <property name="removeAbandoned" value="true"/> <!-- 超时时间;单位为秒。180秒=3分钟 --> <property name="removeAbandonedTimeout" value="7200"/> <!-- 关闭abanded连接时输出错误日志 <property name="logAbandoned" value="true" /> --> </bean> <!-- JPA实体管理器工厂 --> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter"/> <!-- 加入定制化包路径 --> <property name="packagesToScan" value="com.rjsoft.uums.facade.app.entity"/> <property name="jpaProperties"> <props> <prop key="hibernate.current_session_context_class">thread</prop> <prop key="hibernate.hbm2ddl.auto">update</prop><!-- validate/update/create --> <prop key="hibernate.show_sql">false</prop> <prop key="hibernate.format_sql">false</prop> <!-- 建表的命名规则 --> <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop> </props> </property> </bean> <!-- 设置JPA实现厂商的特定属性 --> <bean id="hibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="databasePlatform" value="${hibernate.dialect}"/> </bean> <!-- Jpa 事务配置 --> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <!-- Spring Data Jpa配置 --> <jpa:repositories base-package="com.rjsoft.**.repository" transaction-manager-ref="transactionManager" factory-class="com.rjsoft.common.repository.CustomRepositoryFactoryBean" entity-manager-factory-ref="entityManagerFactory" /> <!-- 使用annotation定义事务 --> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/> <tx:advice id="userTxAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="find*" read-only="true"/> <tx:method name="get*" read-only="true"/> <tx:method name="load*" read-only="true"/> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <aop:config proxy-target-class="true"> <aop:pointcut id="pc" expression="execution(* com.rjsoft..service..*.*(..))"/> <aop:advisor pointcut-ref="pc" advice-ref="userTxAdvice"/> </aop:config> <!-- 类型转换及数据格式化 --> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"/> <!-- 直接把id转换为entity 必须非lazy否则无法注册--> <bean id="domainClassConverter" class="org.springframework.data.repository.support.DomainClassConverter"> <constructor-arg ref="conversionService"/> </bean> <!--设置查询字符串转换器的conversion service--> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="com.rjsoft.common.model.search.utils.SearchableConvertUtils.setConversionService"/> <property name="arguments" ref="conversionService"/> </bean> <!--设置BaseRepositoryImplHelper辅助类所需的entityManagerFactory--> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="com.rjsoft.common.repository.RepositoryHelper.setEntityManagerFactory"/> <property name="arguments" ref="entityManagerFactory"/> </bean> </beans>
我们知道原生的jpa的配置信息是必须放在META-INF目录下面的,并且名字必须叫做persistence.xml,这个叫做persistence-unit,就叫做持久化单元,放在这下面我们感觉不方便,不好,于是Spring提供了
org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
这样一个类,可以让你的随心所欲的起这个配置文件的名字,也可以随心所欲的修改这个文件的位置,只需要在这里指向这个位置就行。然而更加方便的做法是,直接把配置信息就写在这里更好,于是就有了这实体管理器这个bean。使用
<property name="packagesToScan" value="your entity package" />
这个属性来加载我们的entity。
解释“dao”这个bean。这里衍生一下,进行一下名词解释,我们知道dao这个层叫做Data Access Object,数据库访问对象,这是一个广泛的词语,在jpa当中,我们还有一个词语叫做Repository,这里我们一般就用Repository结尾来表示这个dao,比如UserDao,这里我们使用UserRepository
在mybatis中我们一般也不叫dao,mybatis由于使用xml映射文件(当然也提供注解,但是官方文档上面表示在有些地方,比如多表的复杂查询方面,注解还是无解,只能xml),我们一般使用mapper结尾,比如我们也不叫UserDao,而叫UserMapper。
比如:我们的UserRepository和UserRepositoryImpl这两个类就像下面这样来写。
public interface UserRepository extends JpaRepository<User, Integer>{} public class UserRepositoryImpl {}
public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepository<T, ID>, JpaSpecificationExecutor<T>
我们可以看到这个类是实现了JpaRepository接口的,事实上如果我们按照上面的配置,在同一个包下面有UserRepository,但是没有UserRepositoryImpl这个类的话,在运行时期UserRepository这个接口的实现就是上面的SimpleJpaRepository这个接口。而如果有UserRepositoryImpl这个文件的话,那么UserRepository的实现类就是UserRepositoryImpl,而UserRepositoryImpl这个类又是SimpleJpaRepository的子类,如此一来就很好的解决了上面的这个不用写implements的问题。我们通过阅读这个类的源代码可以发现,里面包装了entityManager,底层的调用关系还是entityManager在进行CRUD。
Spring Data JPA在JPA上又做了一层封装,只要编写接口就够了,不用写一行实现代码,CRUD方法啦,分页啦,自动将findByLoginName()的方法定义翻译成适当的QL啦都由它包了:
public interface UserDao extends PagingAndSortingRepository<User, Long> { User findByLoginName(String loginName); }
spring-data-jpa会根据方法的名字来自动生成sql语句,我们只需要按照方法定义的规则即可,上面的方法findByNameAndPassword,spring-data-jpa规定,方法都以findBy开头,sql的where部分就是NameAndPassword
通过上面,基本CRUD和基本的业务逻辑操作都得到了解决,我们要做的工作少到仅仅需要在UserRepository接口中定义几个方法,其他所有的工作都由spring-data-jpa来完成。
@Entity @Table(name="UMS_APPLICATION") public class UmsApp extends UUIDEntity<String>{ private static final long serialVersionUID = 1L; /** * 系统标识符 */ private String sn; /** * 系统名称 */ private String name; /** * 系统Url */ private String url; /** * 系统描述 */ private String description; /** * 系统单点登录标志 */ private Short ssoFlag; // getter,setter
public interface UmsAppRepository extends JpaRepository<UmsApp,String> { @Query("select o from UmsApp o where o.sn=?1") public UmsApp findAppBySn(String appSn); @Query("select o from UmsApp o where o.name=?1") public UmsApp findAppByName(String name); }
@Service("umsAppService") public class UmsAppService { @Autowired private UmsAppRepository umsAppRepository; /** * 保存应用系统信息 */ public UmsApp saveApp(UmsApp app){ if(findAppBySn(app.getSn())!=null){ throw new AppSnExistsException(); } return save(app); }
@NoRepositoryBean public interface CustomRepository<T, ID extends Serializable>extends JpaRepository<T, ID>,JpaSpecificationExecutor<T> { /** * 根据条件查询所有 * 条件 + 分页 + 排序 * * @param searchable * @return */ public Page<T> findAll(Searchable searchable); /** * 根据条件统计所有记录数 * * @param searchable * @return */ public long count(Searchable searchable); /** * 根据主键删除 * * @param ids */ public void delete(ID[] ids); }
注意 @NoRepositoryBean一定要有的
public class CustomRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements CustomRepository<T, ID> { public static final String FIND_QUERY_STRING = "from %s x where 1=1 "; public static final String COUNT_QUERY_STRING = "select count(x) from %s x where 1=1 "; private SearchCallback searchCallback = SearchCallback.DEFAULT; @SuppressWarnings("unused") private final EntityManager entityManager; private final RepositoryHelper repositoryHelper; /** * 查询所有的QL */ private String findAllQL; /** * 统计QL */ private String countAllQL; public CustomRepositoryImpl(Class<T> domainClass, EntityManager entityManager) { super(domainClass, entityManager); this.entityManager = entityManager; repositoryHelper = new RepositoryHelper(domainClass); findAllQL = String.format(FIND_QUERY_STRING, domainClass.getName()); countAllQL = String.format(COUNT_QUERY_STRING, domainClass.getName()); } @Override public List<T> findAll() { return repositoryHelper.findAll(findAllQL); } @Override public List<T> findAll(final Sort sort) { return repositoryHelper.findAll(findAllQL, sort); } @Override public Page<T> findAll(final Pageable pageable) { return new PageImpl<T>( repositoryHelper.<T>findAll(findAllQL, pageable), pageable, repositoryHelper.count(countAllQL) ); } @Override public long count() { return repositoryHelper.count(countAllQL); } public void delete(ID id) { T m = getOne(id); delete(m); } @Override public void delete(T entity) { if (entity == null) { return; } if (entity instanceof LogicDeleteable) { ((LogicDeleteable) entity).markDeleted(); save(entity); } else { super.delete(entity); } } @Override public Page<T> findAll(final Searchable searchable) { List<T> list = repositoryHelper.findAll(findAllQL, searchable, searchCallback); long total = searchable.hasPageable() ? count(searchable) : list.size(); return new PageImpl<T>( list, searchable.getPage(), total ); } @Override public long count(final Searchable searchable) { return repositoryHelper.count(countAllQL, searchable, searchCallback); } @Override public void delete(ID[] ids) { for(ID id:ids){ this.delete(id); } } }
public class CustomRepositoryFactoryBean<T extends JpaRepository<S, ID>, S, ID extends Serializable> extends JpaRepositoryFactoryBean<T, S, ID> { public CustomRepositoryFactoryBean(Class<? extends T> repositoryInterface) { super(repositoryInterface); } @Override protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) { return new CustomRepositoryFactory(entityManager); } private static class CustomRepositoryFactory extends JpaRepositoryFactory { public CustomRepositoryFactory(EntityManager entityManager) { super(entityManager); } @Override protected <T, ID extends Serializable> SimpleJpaRepository<?, ?> getTargetRepository( RepositoryInformation information, EntityManager entityManager) { return new CustomRepositoryImpl<T, ID>((Class<T>) information.getDomainType(), entityManager); } @Override protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {// 5 return CustomRepositoryImpl.class; } } }
public class RepositoryHelper { private static EntityManager entityManager; private Class<?> entityClass; private boolean enableQueryCache = false; /** * @param entityClass 是否开启查询缓存 */ public RepositoryHelper(Class<?> entityClass) { this.entityClass = entityClass; EnableQueryCache enableQueryCacheAnnotation = AnnotationUtils.findAnnotation(entityClass, EnableQueryCache.class); boolean enableQueryCache = false; if (enableQueryCacheAnnotation != null) { enableQueryCache = enableQueryCacheAnnotation.value(); } this.enableQueryCache = enableQueryCache; } public static void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) { entityManager = SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactory); } public static EntityManager getEntityManager() { Assert.notNull(entityManager, "entityManager must null, please see " + "[com.rjsoft.common.repository.RepositoryHelper#setEntityManagerFactory]"); return entityManager; } public static void flush() { getEntityManager().flush(); } public static void clear() { flush(); getEntityManager().clear(); } /** * <p>ql条件查询<br/> * searchCallback默认实现请参考 <br/> * <p/> * @param ql * @param searchable 查询条件、分页 排序 * @param searchCallback 查询回调 自定义设置查询条件和赋值 * @return */ @SuppressWarnings("unchecked") public <M> List<M> findAll(final String ql, final Searchable searchable, final SearchCallback searchCallback) { assertConverted(searchable); StringBuilder s = new StringBuilder(ql); searchCallback.prepareQL(s, searchable); searchCallback.prepareOrder(s, searchable); Query query = getEntityManager().createQuery(s.toString()); applyEnableQueryCache(query); searchCallback.setValues(query, searchable); searchCallback.setPageable(query, searchable); return query.getResultList(); } /** * <p>按条件统计<br/> * * @param ql * @param searchable * @param searchCallback * @return */ public long count(final String ql, final Searchable searchable, final SearchCallback searchCallback) { assertConverted(searchable); StringBuilder s = new StringBuilder(ql); searchCallback.prepareQL(s, searchable); Query query = getEntityManager().createQuery(s.toString()); applyEnableQueryCache(query); searchCallback.setValues(query, searchable); return (Long) query.getSingleResult(); } /** * 按条件查询一个实体 * * @param ql * @param searchable * @param searchCallback * @return */ public <M> M getOne(final String ql, final Searchable searchable, final SearchCallback searchCallback) { assertConverted(searchable); StringBuilder s = new StringBuilder(ql); searchCallback.prepareQL(s, searchable); searchCallback.prepareOrder(s, searchable); Query query = getEntityManager().createQuery(s.toString()); applyEnableQueryCache(query); searchCallback.setValues(query, searchable); searchCallback.setPageable(query, searchable); query.setMaxResults(1); List<M> result = query.getResultList(); if (result.size() > 0) { return result.get(0); } return null; } /** * @param ql * @param params * @param <M> * @return * @see RepositoryHelper#findAll(String, org.springframework.data.domain.Pageable, Object...) */ public <M> List<M> findAll(final String ql, final Object... params) { //此处必须 (Pageable) null 否则默认有调用自己了 可变参列表 return findAll(ql, (Pageable) null, params); } /** * <p>根据ql和按照索引顺序的params执行ql,pageable存储分页信息 null表示不分页<br/> * * @param pageable null表示不分页 * @param params * @param <M> * @return */ @SuppressWarnings("unchecked") public <M> List<M> findAll(final String ql, final Pageable pageable, final Object... params) { Query query = getEntityManager().createQuery(ql + prepareOrder(pageable != null ? pageable.getSort() : null)); applyEnableQueryCache(query); setParameters(query, params); if (pageable != null) { query.setFirstResult((int) pageable.getOffset()); query.setMaxResults(pageable.getPageSize()); } return query.getResultList(); } /** * <p>根据ql和按照索引顺序的params执行ql,sort存储排序信息 null表示不排序<br/> * * @param ql * @param sort null表示不排序 * @param params * @param <M> * @return */ @SuppressWarnings("unchecked") public <M> List<M> findAll(final String ql, final Sort sort, final Object... params) { Query query = getEntityManager().createQuery(ql + prepareOrder(sort)); applyEnableQueryCache(query); setParameters(query, params); return query.getResultList(); } /** * <p>根据ql和按照索引顺序的params查询一个实体<br/> * * @param ql * @param params * @param <M> * @return */ public <M> M findOne(final String ql, final Object... params) { List<M> list = findAll(ql, PageRequest.of(0, 1), params); if (list.size() > 0) { return list.get(0); } return null; } /** * <p>根据ql和按照索引顺序的params执行ql统计<br/> * * @param ql * @param params * @return */ public long count(final String ql, final Object... params) { Query query = entityManager.createQuery(ql); applyEnableQueryCache(query); setParameters(query, params); return (Long) query.getSingleResult(); } /** * <p>执行批处理语句.如 之间insert, update, delete 等.<br/> * * @param ql * @param params * @return */ public int batchUpdate(final String ql, final Object... params) { Query query = getEntityManager().createQuery(ql); setParameters(query, params); return query.executeUpdate(); } /** * 按顺序设置Query参数 * * @param query * @param params */ public void setParameters(Query query, Object[] params) { if (params != null) { for (int i = 0; i < params.length; i++) { query.setParameter(i + 1, params[i]); } } } /** * 拼排序 * @param sort * @return */ public String prepareOrder(Sort sort) { if (sort == null || !sort.iterator().hasNext()) { return ""; } StringBuilder orderBy = new StringBuilder(""); orderBy.append(" order by "); orderBy.append(sort.toString().replace(":", " ")); return orderBy.toString(); } public <T> JpaEntityInformation<T, ?> getMetadata(Class<T> entityClass) { return JpaEntityInformationSupport.getEntityInformation(entityClass, entityManager); } public String getEntityName(Class<?> entityClass) { return getMetadata(entityClass).getEntityName(); } private void assertConverted(Searchable searchable) { if (!searchable.isConverted()) { searchable.convert(this.entityClass); } } public void applyEnableQueryCache(Query query) { if (enableQueryCache) { query.setHint("org.hibernate.cacheable", true);//开启查询缓存 } } }
<jpa:repositories base-package="com.rjsoft.**.repository" transaction-manager-ref="transactionManager" factory-class="com.rjsoft.common.repository.CustomRepositoryFactoryBean" entity-manager-factory-ref="entityManagerFactory" /> <!-- 直接把id转换为entity 必须非lazy否则无法注册--> <bean id="domainClassConverter" class="org.springframework.data.repository.support.DomainClassConverter"> <constructor-arg ref="conversionService"/> </bean> <!--设置查询字符串转换器的conversion service--> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="com.rjsoft.common.model.search.utils.SearchableConvertUtils.setConversionService"/> <property name="arguments" ref="conversionService"/> </bean> <!--设置BaseRepositoryImplHelper辅助类所需的entityManagerFactory--> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="com.rjsoft.common.repository.RepositoryHelper.setEntityManagerFactory"/> <property name="arguments" ref="entityManagerFactory"/> </bean>