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类
package com.esom.tech.springjpa.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
@Entity
@DynamicInsert
@DynamicUpdate//生成的SQL中涉及的字段只包含User类中修改的属性所对应的表字段
@Table(name="MA_USER")
publicclass User {
@Id
@Column(name = "ID")
@SequenceGenerator(name="USER_ID_GENERATOR", sequenceName="SEQ_USER_ID",allocationSize=1)
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="USER_ID_GENERATOR")
private Long id;
/**
* 如果不指定表字段,会自动映射为USERNAME
* 并且在加载运行时,发现没有该表字段,会自动添加创建(如果表,Sequence没有也会创建)。
* 对应ORACLE,创建的的类型对应关系:
* String VARCHAR2(255 CHAR)
* Long NUMBER(19)
* Integer NUMBER(10)
* java.sql.Date DATE
* java.sql.Time DATE
* java.util.Date TIMESTAMP(6)
* java.sql.Timestamp TIMESTAMP(6)
*/
@Column(name = "USER_NAME", unique = true)
private String userName;
@Column(name = "FIRST_NAME")
private String firstName;
@Column(name = "LAST_NAME")
private String lastName;
@Column(name = "AGE")
private Integer age;
@Override
public String toString() {
return String.format("Entity of type %s with id: %s", this.getClass()
.getName(), getId());
}
@Override
publicboolean equals(Object obj) {
if (null == obj) {
returnfalse;
}
if (this == obj) {
returntrue;
}
if (!getClass().equals(obj.getClass())) {
returnfalse;
}
returnnull == this.getId() ? false : this.getId().equals(((User)obj).getId());
}
// 忽略所有get、set方法
}
2. Repository接口
package com.esom.tech.springjpa.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import com.esom.tech.springjpa.domain.User;
publicinterface UserRepository extends CrudRepository<User, Long>, JpaSpecificationExecutor<User>{
/**
* 根据方法名解析
* @param lastname
* @return
*/
List<User> findByLastName(String ln);
/**
* 根据@Query和命名参数解析
* 注意这里是HSQL,所以用User(而非ma_user), lastName(而非first_name)
* @param name
* @return
*/
@Query(" from User u where u.firstName = :name or u.lastName = :name ")
public List<User> findByFirstNameOrLastName(@Param("name") String name);
/**
* 根据@Query和占位符解析
* @param firstname
* @return
*/
@Query(" from User u where u.firstName = ?1 and lastName = ?2 ")
List<User> findByFirstNameAndLastName(String fb, String ln);
}
3. 一小陀配置
配置repository和服务bean,demo-repository-context.xml
<?xmlversion="1.0"encoding="UTF8"?>
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<importresource="classpath*:/META-INF/spring/application-context-root.xml"/>
<!--配置jpa repository,核心部分-->
<jpa:repositoriesbase-package="com.esom.tech.springjpa.repository"entity-manager-factory-ref="entityManagerFactory"
transaction-manager-ref="transactionManager"/>
<!--配置服务bean-->
<context:component-scanbase-package="com.esom.tech.springjpa.service"/>
</beans>
配置数据源及EntityManager,application-context-root.xml
<?xmlversion="1.0"encoding="UTF8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx"xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--Data Source-->
<beanid="dataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">
<propertyname="driverClassName"value="oracle.jdbc.driver.OracleDriver"/>
<propertyname="url"value="jdbc:oracle:thin:@171.22.70.28:1521:gbst"/>
<propertyname="username"value="username"/>
<propertyname="password"value="password"/>
<propertyname="initialSize"value="1"/>
<propertyname="maxActive"value="2"/>
</bean>
<!-- Parent Entity Manager : Hibernate 实现 -->
<beanid="parentEntityManagerFactory"class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
abstract="true">
<propertyname="jpaVendorAdapter">
<beanclass="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<propertyname="database"value="ORACLE"/>
<propertyname="showSql"value="true"/>
<propertyname="generateDdl"value="true"/>
</bean>
</property>
</bean>
<!-- Entity Manager -->
<beanid="entityManagerFactory"parent="parentEntityManagerFactory">
<propertyname="dataSource"ref="dataSource"/>
<propertyname="packagesToScan">
<array>
<!--entity扫描目录,可多个value节点-->
<value>com.esom.tech.springjpa.domain</value>
</array>
</property>
</bean>
<!-- Transaction Manager -->
<beanid="transactionManager"class="org.springframework.orm.jpa.JpaTransactionManager">
<propertyname="entityManagerFactory"ref="entityManagerFactory"/>
</bean>
<tx:adviceid="businessTxAdvise"transaction-manager="transactionManager"/>
<aop:config>
<aop:pointcutid="businessPointcut"
expression="execution(* com.esom.tech..*.*(..))"/>
<aop:advisoradvice-ref="businessTxAdvise"pointcut-ref="businessPointcut"/>
</aop:config>
</beans>
根据项目具体情况配置,不一定是Hibernate EntityManager及ORACLE
4. 业务层接口
package com.esom.tech.springjpa.service;
import com.esom.tech.springjpa.domain.User;
publicinterface UserService {
//保存User
public User saveUser(User user);
//是否存在客户
boolean hasUser(Long id);
}
5. 业务层接口实现类
package com.esom.tech.springjpa.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.esom.tech.springjpa.domain.User;
import com.esom.tech.springjpa.repository.UserRepository;
import com.esom.tech.springjpa.service.UserService;
@Service
publicclass UserServiceImpl implements UserService{
@Autowired
private UserRepository userRepository;
publicboolean hasUser(Long id) {
User user = userRepository.findOne(id);
return user != null ? true:false;
}
public User saveUser(User user) {
return userRepository.save(user);
}
}
6. 测试代码
package com.esom.tech.springjpademo;
importstatic org.junit.Assert.*;
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
//import org.springframework.test.context.transaction.TransactionConfiguration;
//import org.springframework.transaction.annotation.Transactional;
import com.esom.tech.springjpa.domain.User;
import com.esom.tech.springjpa.repository.UserRepository;
import com.esom.tech.springjpa.service.UserService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:/META-INF/spring/springjpa/demo-repository-context.xml" })
//@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
//@Transactional
publicclass SpringJpaTest {
@Autowired
UserRepository repository;
@Autowired
UserService userService;
User user;
//每个@Test方法都会先执行init()一遍
@Before
publicvoid init(){
user = new User();
user.setAge(28);
user.setFirstName("Lios");
user.setLastName("Lin");
user.setUserName("Lios Lin");
}
// crud方法测试
@Test
publicvoid testCrud(){
//第一次,新增一条记录,新增后user.id会给赋值
repository.save(user);
//第二次,不做保存,因为user.id有值,会根据user.id查询数据库是否存在记录,对有记录并且字段值没变动的忽略保存操作
repository.save(user);
//第三次,字段值有变动(包括置为null),做更新保存
user.setAge(68);
repository.save(user);
User user2 = repository.findOne(user.getId());
assertEquals(user.getAge(),user2.getAge());
assertEquals(user,user2);
}
// method query测试
@Test
publicvoid testMethodQuery() throws Exception {
repository.save(user);
List<User> users = repository.findByLastName("Lin");
assertNotNull(users);
assertTrue(users.contains(user));
}
// named query测试
@Test
publicvoid testNameQuery() throws Exception {
repository.save(user);
List<User> users = repository.findByFirstNameOrLastName("Lin");
assertTrue(users.contains(user));
}
// criteria query测试
@Test
publicvoid testCriteriaQuery() throws Exception {
repository.save(user);
List<User> users = repository.findAll(new Specification<User>() {
public Predicate toPredicate(Root<User> root,
CriteriaQuery<?> query,CriteriaBuilder cb) {
return cb.equal(root.get("lastName"), user.getLastName());
}
});
assertTrue(users.contains(user));
}
// 其他 query测试
@Test
publicvoid testOtherQuery() throws Exception {
repository.save(user);
//占位符查询
List<User> users = repository.findByFirstNameAndLastName(user.getFirstName(), user.getLastName());
assertTrue(users.contains(user));
}
// service测试
@Test
publicvoid testService() throws Exception {
userService.saveUser(user);
assertTrue(userService.hasUser(user.getId()));
}
}
这个项目结构如下(附件一并带上源码):
跑JUnit测试,绿了,要使生活过的去,哪怕测试有点绿,,,
至于pom.xml为什么是红呢,这是因为maven引用oracle jdbc驱动引起的,下面章节5会说到。
4、Repository核心接口
上面代码可以看到持久层UserRepository继承了CrudRepository和JpaSpecificationExecutor接口(注意这些接口都不需要实现),而CrudRepository又继承了顶级接口Repository。
我们看下接口CrudRepository中的办法:
publicinterface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
<S extends T> S save(S entity);
<S extends T> Iterable<S> save(Iterable<S> entities);
T findOne(ID id);
boolean exists(ID id);
Iterable<T> findAll();
Iterable<T> findAll(Iterable<ID> ids);
long count();
void (ID id);
void (T entity);
void (Iterable<? extends T> entities);
void All();
}
继承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 将查询标识为修改查询
@Modifying
@Query("update User a set a.age = ?1 where a.id = ?2")
publicint updateUserAge(int age, int id);
5、示例构建过程遇到的一些问题
1、使用maven管理项目包的依赖,如果项目没用到maven,根据pom.xml的配置引用对应包到项目
<project>
...
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>3.2.0.RELEASE</spring.version>
<slf4j.version>1.6.6</slf4j.version>
</properties>
<dependencies>
<!-- JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<!-- J2EE -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<!--dbcp-->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<!-- HSQL
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
-->
<!-- hibernate -->
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.6.Final</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.1</version>
</dependency>
<!--oracle-->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3.0</version>
</dependency>
</dependencies>
...
</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声明依赖
<project>
...
<repositories>
<repository>
<id>my-repo</id>
<name>my-repo</name>
<url>http://localhost:8082/nexus/content/groups/public</url>
</repository>
</repositories>
<dependencies>
...
<!--oracle-->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3.0</version>
</dependency>
...
</dependencies>
...
</project>
JDBC驱动就添加到工程中了。
第二种:通过构建自己的私人仓库实现,后续介绍。