1、前言

最近项目需要用到Spring Data JPA,经过一段时间的学习和整理,做如下备忘笔记,也供读者了解和使用该框架提供指导。

2、Spring Data JPA简介

介绍:针对关系型数据库,KV数据库,Document数据库,Graph数据库,Map-Reduce等主流数据库,采用统一技术进行访问,并且尽可能简化访问手段,让数据的访问变得更加方便。Spring Data由多个子项目组成,支持CouchDB、MongoDB、Neo4J、Hadoop、HBase、Cassandra、JPA等。


学习资料:SpringData主页

SpringData:http://www.springsource.org/spring-data
SpringDataJPA:http://www.springsource.org/spring-data/jpa

SpringDataJPA 指南文档:http://static.springsource.org/spring-data/data-jpa/docs/current/reference/html/

3、实践示例

本实践示例代码基于 Hibernate EntityManager 开发,但是读者几乎不用修改任何代码,便可以非常容易地切换到其他JPA 框架,因为代码中使用到的都是JPA 规范提供的接口/ 类。


示例用到数据源为Oracle,包依赖用Maven管理,在前段时间构建的基础Maven项目『Maven笔记(2)』上加以实现:


Spring Data JPA 极大简化了数据库访问层代码,只要3步:

1. 编写Entity类,依照JPA规范,定义实体
2. 编写Repository接口,依靠SpringData规范,定义数据访问接口(只要接口,不需要任何实现)
3. 编写一小陀配置文件(Spring极大地简化了配置方式)


另加3步实现业务层及测试类:

4. 编写业务层接口

5. 编写业务层接口实现类

6. 编写测试代码


示例主要涉及六个文件:业务层包含一个接口和一个实现;持久层包含一个接口、一个实体类;另外加上一个JPA 配置文件和一个测试类。相关类/接口/配置如下:


1. Entity类

Java代码  
  1. package com.esom.tech.springjpa.domain;  

  2. import javax.persistence.Column;  

  3. import javax.persistence.Entity;  

  4. import javax.persistence.GeneratedValue;  

  5. import javax.persistence.GenerationType;  

  6. import javax.persistence.Id;  

  7. import javax.persistence.SequenceGenerator;  

  8. import javax.persistence.Table;  

  9. import org.hibernate.annotations.DynamicInsert;  

  10. import org.hibernate.annotations.DynamicUpdate;  

  11. @Entity

  12. @DynamicInsert

  13. @DynamicUpdate//生成的SQL中涉及的字段只包含User类中修改的属性所对应的表字段

  14. @Table(name="MA_USER")  

  15. publicclass User {  

  16. @Id

  17. @Column(name = "ID")  

  18. @SequenceGenerator(name="USER_ID_GENERATOR", sequenceName="SEQ_USER_ID",allocationSize=1)  

  19. @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="USER_ID_GENERATOR")  

  20. private Long id;  

  21. /**

  22.     * 如果不指定表字段,会自动映射为USERNAME

  23.     * 并且在加载运行时,发现没有该表字段,会自动添加创建(如果表,Sequence没有也会创建)。

  24.     * 对应ORACLE,创建的的类型对应关系:

  25.     *      String  VARCHAR2(255 CHAR)

  26.     *      Long    NUMBER(19)

  27.     *      Integer NUMBER(10)

  28.     *      java.sql.Date   DATE

  29.     *      java.sql.Time   DATE

  30.     *      java.util.Date  TIMESTAMP(6)

  31.     *      java.sql.Timestamp  TIMESTAMP(6)    

  32.     */

  33. @Column(name = "USER_NAME", unique = true)  

  34. private String userName;  

  35. @Column(name = "FIRST_NAME")  

  36. private String firstName;  

  37. @Column(name = "LAST_NAME")  

  38. private String lastName;  

  39. @Column(name = "AGE")  

  40. private Integer age;  

  41. @Override

  42. public String toString() {  

  43. return String.format("Entity of type %s with id: %s", this.getClass()  

  44.                .getName(), getId());  

  45.    }  

  46. @Override

  47. publicboolean equals(Object obj) {  

  48. if (null == obj) {  

  49. returnfalse;  

  50.        }  

  51. if (this == obj) {  

  52. returntrue;  

  53.        }  

  54. if (!getClass().equals(obj.getClass())) {  

  55. returnfalse;  

  56.        }  

  57. returnnull == this.getId() ? false : this.getId().equals(((User)obj).getId());  

  58.    }  

  59. // 忽略所有get、set方法

  60. }  


2. Repository接口

Java代码  
  1. package com.esom.tech.springjpa.repository;  

  2. import java.util.List;  

  3. import org.springframework.data.jpa.repository.JpaSpecificationExecutor;  

  4. import org.springframework.data.jpa.repository.Query;  

  5. import org.springframework.data.repository.CrudRepository;  

  6. import org.springframework.data.repository.query.Param;  

  7. import com.esom.tech.springjpa.domain.User;  

  8. publicinterface UserRepository extends CrudRepository, JpaSpecificationExecutor{  

  9. /**

  10.     * 根据方法名解析

  11.     * @param lastname

  12.     * @return

  13.     */

  14.    List findByLastName(String ln);  

  15. /**

  16.     * 根据@Query和命名参数解析

  17.     * 注意这里是HSQL,所以用User(而非ma_user), lastName(而非first_name)

  18.     * @param name

  19.     * @return

  20.     */

  21. @Query(" from User u where u.firstName = :name or u.lastName = :name ")  

  22. public List findByFirstNameOrLastName(@Param("name") String name);  

  23. /**

  24.     * 根据@Query和占位符解析

  25.     * @param firstname

  26.     * @return

  27.     */

  28. @Query(" from User u where u.firstName = ?1 and lastName = ?2 ")  

  29.    List findByFirstNameAndLastName(String fb, String ln);  

  30. }  


3. 一小陀配置


配置repository和服务bean,demo-repository-context.xml

Xml代码  
  1. xmlversion="1.0"encoding="UTF8"?>

  2. <beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  3. xmlns:context="http://www.springframework.org/schema/context"xmlns:jpa="http://www.springframework.org/schema/data/jpa"

  4. xsi:schemaLocation="  

  5.        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  

  6.        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd  

  7.        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

  8. <importresource="classpath*:/META-INF/spring/application-context-root.xml"/>

  9. <jpa:repositoriesbase-package="com.esom.tech.springjpa.repository"entity-manager-factory-ref="entityManagerFactory"

  10. transaction-manager-ref="transactionManager"/>

  11. <context:component-scanbase-package="com.esom.tech.springjpa.service"/>

  12. beans>


配置数据源及EntityManager,application-context-root.xml

Xml代码  
  1. xmlversion="1.0"encoding="UTF8"?>

  2. <beansxmlns="http://www.springframework.org/schema/beans"

  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:jdbc="http://www.springframework.org/schema/jdbc"

  4. xmlns:tx="http://www.springframework.org/schema/tx"xmlns:util="http://www.springframework.org/schema/util"

  5. xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"

  6. xsi:schemaLocation="  

  7.        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd  

  8.        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  

  9.        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd  

  10.        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd  

  11.        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd  

  12.        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

  13. <beanid="dataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">

  14. <propertyname="driverClassName"value="oracle.jdbc.driver.OracleDriver"/>

  15. <propertyname="url"value="jdbc:oracle:thin:@171.22.70.28:1521:gbst"/>

  16. <propertyname="username"value="username"/>

  17. <propertyname="password"value="password"/>

  18. <propertyname="initialSize"value="1"/>

  19. <propertyname="maxActive"value="2"/>

  20. bean>

  21. <beanid="parentEntityManagerFactory"class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"

  22. abstract="true">

  23. <propertyname="jpaVendorAdapter">

  24. <beanclass="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">

  25. <propertyname="database"value="ORACLE"/>

  26. <propertyname="showSql"value="true"/>

  27. <propertyname="generateDdl"value="true"/>

  28. bean>

  29. property>

  30. bean>

  31. <beanid="entityManagerFactory"parent="parentEntityManagerFactory">

  32. <propertyname="dataSource"ref="dataSource"/>

  33. <propertyname="packagesToScan">

  34. <array>

  35. <value>com.esom.tech.springjpa.domainvalue>

  36. array>

  37. property>

  38. bean>

  39. <beanid="transactionManager"class="org.springframework.orm.jpa.JpaTransactionManager">

  40. <propertyname="entityManagerFactory"ref="entityManagerFactory"/>

  41. bean>

  42. <tx:adviceid="businessTxAdvise"transaction-manager="transactionManager"/>

  43. <aop:config>

  44. <aop:pointcutid="businessPointcut"

  45. expression="execution(* com.esom.tech..*.*(..))"/>

  46. <aop:advisoradvice-ref="businessTxAdvise"pointcut-ref="businessPointcut"/>

  47. aop:config>

  48. beans>

根据项目具体情况配置,不一定是Hibernate EntityManager及ORACLE


4. 业务层接口

Java代码  
  1. package com.esom.tech.springjpa.service;  

  2. import com.esom.tech.springjpa.domain.User;  

  3. publicinterface UserService {  

  4. //保存User

  5. public User saveUser(User user);  

  6. //是否存在客户

  7. boolean hasUser(Long id);  

  8. }  


5. 业务层接口实现类

Java代码  
  1. package com.esom.tech.springjpa.service.impl;  

  2. import org.springframework.beans.factory.annotation.Autowired;  

  3. import org.springframework.stereotype.Service;  

  4. import com.esom.tech.springjpa.domain.User;  

  5. import com.esom.tech.springjpa.repository.UserRepository;  

  6. import com.esom.tech.springjpa.service.UserService;  

  7. @Service

  8. publicclass UserServiceImpl implements UserService{  

  9. @Autowired

  10. private UserRepository userRepository;  

  11. publicboolean hasUser(Long id) {  

  12.        User user = userRepository.findOne(id);  

  13. return user != null ? true:false;  

  14.    }  

  15. public User saveUser(User user) {  

  16. return userRepository.save(user);  

  17.    }  

  18. }  


6. 测试代码

Java代码  
  1. package com.esom.tech.springjpademo;  

  2. importstatic org.junit.Assert.*;  

  3. import java.util.List;  

  4. import javax.persistence.criteria.CriteriaBuilder;  

  5. import javax.persistence.criteria.CriteriaQuery;  

  6. import javax.persistence.criteria.Predicate;  

  7. import javax.persistence.criteria.Root;  

  8. import org.junit.Before;  

  9. import org.junit.Test;  

  10. import org.junit.runner.RunWith;  

  11. import org.springframework.beans.factory.annotation.Autowired;  

  12. import org.springframework.data.jpa.domain.Specification;  

  13. import org.springframework.test.context.ContextConfiguration;  

  14. import org.springframework.test.context.junit4.SpringJUnit4Cla***unner;  

  15. //import org.springframework.test.context.transaction.TransactionConfiguration;

  16. //import org.springframework.transaction.annotation.Transactional;

  17. import com.esom.tech.springjpa.domain.User;  

  18. import com.esom.tech.springjpa.repository.UserRepository;  

  19. import com.esom.tech.springjpa.service.UserService;  

  20. @RunWith(SpringJUnit4Cla***unner.class)  

  21. @ContextConfiguration(locations = {"classpath*:/META-INF/spring/springjpa/demo-repository-context.xml" })  

  22. //@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)

  23. //@Transactional

  24. publicclass SpringJpaTest {  

  25. @Autowired

  26.    UserRepository repository;  

  27. @Autowired

  28.    UserService userService;  

  29.    User user;  

  30. //每个@Test方法都会先执行init()一遍

  31. @Before

  32. publicvoid init(){  

  33.        user = new User();  

  34.        user.setAge(28);  

  35.        user.setFirstName("Lios");  

  36.        user.setLastName("Lin");  

  37.        user.setUserName("Lios Lin");  

  38.    }  

  39. // crud方法测试

  40. @Test

  41. publicvoid testCrud(){  

  42. //第一次,新增一条记录,新增后user.id会给赋值

  43.        repository.save(user);    

  44. //第二次,不做保存,因为user.id有值,会根据user.id查询数据库是否存在记录,对有记录并且字段值没变动的忽略保存操作

  45.        repository.save(user);  

  46. //第三次,字段值有变动(包括置为null),做更新保存

  47.        user.setAge(68);  

  48.        repository.save(user);  

  49.        User user2 = repository.findOne(user.getId());  

  50.        assertEquals(user.getAge(),user2.getAge());  

  51.        assertEquals(user,user2);  

  52.    }  

  53. // method query测试

  54. @Test

  55. publicvoid testMethodQuery() throws Exception {  

  56.        repository.save(user);  

  57.        List users = repository.findByLastName("Lin");  

  58.        assertNotNull(users);  

  59.        assertTrue(users.contains(user));  

  60.    }  

  61. // named query测试

  62. @Test

  63. publicvoid testNameQuery() throws Exception {  

  64.        repository.save(user);  

  65.        List users = repository.findByFirstNameOrLastName("Lin");  

  66.        assertTrue(users.contains(user));  

  67.    }  

  68. // criteria query测试

  69. @Test

  70. publicvoid testCriteriaQuery() throws Exception {  

  71.        repository.save(user);  

  72.        List users = repository.findAll(new Specification() {  

  73. public Predicate toPredicate(Root root,  

  74.                    CriteriaQuery query,CriteriaBuilder cb) {  

  75. return cb.equal(root.get("lastName"), user.getLastName());  

  76.            }  

  77.        });  

  78.        assertTrue(users.contains(user));  

  79.    }  

  80. // 其他 query测试

  81. @Test

  82. publicvoid testOtherQuery() throws Exception {  

  83.        repository.save(user);  

  84. //占位符查询

  85.        List users = repository.findByFirstNameAndLastName(user.getFirstName(), user.getLastName());  

  86.        assertTrue(users.contains(user));  

  87.    }  

  88. // service测试

  89. @Test

  90. publicvoid testService() throws Exception {  

  91.        userService.saveUser(user);  

  92.        assertTrue(userService.hasUser(user.getId()));  

  93.    }  

  94. }  


这个项目结构如下(附件一并带上源码):



跑JUnit测试,绿了,要使生活过的去,哪怕测试有点绿,,,

至于pom.xml为什么是红呢,这是因为maven引用oracle jdbc驱动引起的,下面章节5会说到。


4、Repository核心接口

上面代码可以看到持久层UserRepository继承了CrudRepository和JpaSpecificationExecutor接口(注意这些接口都不需要实现),而CrudRepository又继承了顶级接口Repository。


我们看下接口CrudRepository中的办法:

Java代码  
  1. publicinterface CrudRepositoryextends Serializable> extends Repository {  

  2.    extends T> S save(S entity);  

  3.    extends T> Iterable save(Iterable entities);  

  4.    T findOne(ID id);  

  5. boolean exists(ID id);  

  6.    Iterable findAll();  

  7.    Iterable findAll(Iterable ids);  

  8. long count();  

  9. void (ID id);  

  10. void (T entity);  

  11. void (Iterableextends T> entities);  

  12. void All();  

  13. }  

继承Repository,实现了一组CRUD相关的方法


其他核心接口说明:

PagingAndSortingRepository: 继承CrudRepository,实现了一组分页排序相关的方法

JpaRepository: 继承PagingAndSortingRepository,实现一组JPA规范相关的方法

JpaSpecificationExecutor: 比较特殊,不属于Repository体系,实现一组JPA Criteria查询相关的

这些接口都不需要写任何实现类,Spring Data Jpa框架帮你搞定这一切。


另外说下UserRepository的一些query的其他用法:

1)Method Query: 方法级别的查询,针对 findBy, find, readBy, read, getBy等

前缀的方法,解析方法字符串,生成查询语句,如下图(图来自于互联网):



2)Named Query: 针对一些复杂的SQL,支持原生SQL方式,进行查询,保证性能
3)Criteria Query: 支持JPA标准中的Criteria Query

4)@Modifying 将查询标识为修改查询

Java代码  
  1. @Modifying

  2. @Query("update User a set a.age = ?1 where a.id = ?2")  

  3. publicint updateUserAge(int age, int id);  



5、示例构建过程遇到的一些问题

1、使用maven管理项目包的依赖,如果项目没用到maven,根据pom.xml的配置引用对应包到项目

Xml代码  
  1. <project>

  2. ...  

  3. <properties>

  4. <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>

  5. <spring.version>3.2.0.RELEASEspring.version>

  6. <slf4j.version>1.6.6slf4j.version>

  7. properties>

  8. <dependencies>

  9. <dependency>

  10. <groupId>junitgroupId>

  11. <artifactId>junitartifactId>

  12. <version>4.7version>

  13. <scope>testscope>

  14. dependency>

  15. <dependency>

  16. <groupId>javax.servletgroupId>

  17. <artifactId>servlet-apiartifactId>

  18. <version>2.5version>

  19. <scope>providedscope>

  20. dependency>

  21. <dependency>

  22. <groupId>javax.transactiongroupId>

  23. <artifactId>jtaartifactId>

  24. <version>1.1version>

  25. <scope>providedscope>

  26. dependency>

  27. <dependency>

  28. <groupId>org.springframeworkgroupId>

  29. <artifactId>spring-ormartifactId>

  30. <version>${spring.version}version>

  31. dependency>

  32. <dependency>

  33. <groupId>org.springframeworkgroupId>

  34. <artifactId>spring-txartifactId>

  35. <version>${spring.version}version>

  36. dependency>

  37. <dependency>

  38. <groupId>org.springframeworkgroupId>

  39. <artifactId>spring-contextartifactId>

  40. <version>${spring.version}version>

  41. dependency>

  42. <dependency>

  43. <groupId>org.springframeworkgroupId>

  44. <artifactId>spring-coreartifactId>

  45. <version>${spring.version}version>

  46. dependency>

  47. <dependency>

  48. <groupId>org.springframework.datagroupId>

  49. <artifactId>spring-data-jpaartifactId>

  50. <version>1.2.0.RELEASEversion>

  51. dependency>

  52. <dependency>

  53. <groupId>org.springframeworkgroupId>

  54. <artifactId>spring-testartifactId>

  55. <version>${spring.version}version>

  56. <scope>testscope>

  57. dependency>

  58. <dependency>

  59. <groupId>org.springframeworkgroupId>

  60. <artifactId>spring-aspectsartifactId>

  61. <version>${spring.version}version>

  62. dependency>

  63. <dependency>

  64. <groupId>commons-dbcpgroupId>

  65. <artifactId>commons-dbcpartifactId>

  66. <version>1.4version>

  67. dependency>

  68.    

  69. <dependency>

  70. <groupId>org.hibernate.javax.persistencegroupId>

  71. <artifactId>hibernate-jpa-2.0-apiartifactId>

  72. <version>1.0.0.Finalversion>

  73. dependency>

  74. <dependency>

  75. <groupId>org.hibernategroupId>

  76. <artifactId>hibernate-entitymanagerartifactId>

  77. <version>4.1.6.Finalversion>

  78. dependency>

  79. <dependency>

  80. <groupId>org.slf4jgroupId>

  81. <artifactId>slf4j-log4j12artifactId>

  82. <version>${slf4j.version}version>

  83. <scope>runtimescope>

  84. dependency>

  85. <dependency>

  86. <groupId>org.aspectjgroupId>

  87. <artifactId>aspectjweaverartifactId>

  88. <version>1.7.1version>

  89. dependency>

  90. <dependency>

  91. <groupId>com.oraclegroupId>

  92. <artifactId>ojdbc6artifactId>

  93. <version>11.2.0.3.0version>

  94. dependency>

  95. dependencies>

  96. ...  

  97. project>



2、用Maven管理Oracle  JDBC驱动包有点特殊。直接声明依赖,pom.xml文件会提示找不到依赖,这是因为Oracle JDBC驱动包是需要Oracle官方授权才能从Maven中央库下载,我们可以通过2种方法解决:


第一种:首先,下载Oracle的jdbc驱动包ojdbc6.jar,

下载地址:http://www.oracle.com/technetwork/database/features/jdbc/index-091264.html,

这里下载的版本是11.2.0.1.0。

然后,通过命令行执行命令将包安装本地库中去:mvn install:install-file -DgroupId=com.oracle -DartifactId=ojdbc6 -Dversion=11.2.0.1.0 -Dpackaging=jar -Dfile=D:\ojdbc6.jar。

最后,在pom.xml声明依赖

Xml代码  
  1. <project>

  2. ...  

  3. <repositories>

  4. <repository>

  5. <id>my-repoid>

  6. <name>my-reponame>

  7. <url>http://localhost:8082/nexus/content/groups/publicurl>

  8. repository>

  9. repositories>

  10. <dependencies>

  11.    ...  

  12. <dependency>

  13. <groupId>com.oraclegroupId>

  14. <artifactId>ojdbc6artifactId>

  15. <version>11.2.0.3.0version>

  16. dependency>

  17.    ...  

  18. dependencies>

  19. ...  

  20. project>


JDBC驱动就添加到工程中了。


第二种:通过构建自己的私人仓库实现,后续介绍。