Spring Data JPA 笔记

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步:

扫描二维码关注公众号,回复: 766790 查看本文章

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")
public class 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
	public boolean equals(Object obj) {
		if (null == obj) {
			return false;
		}
		if (this == obj) {
			return true;
		}
		if (!getClass().equals(obj.getClass())) {
			return false;
		}
		return null == 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;

public interface 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

<?xml version="1.0" encoding="UTF8"?>
<beans xmlns="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">

	<import resource="classpath*:/META-INF/spring/application-context-root.xml" />
	
	<!--配置jpa repository,核心部分-->
	<jpa:repositories base-package="com.esom.tech.springjpa.repository" entity-manager-factory-ref="entityManagerFactory"
			transaction-manager-ref="transactionManager" />  

	<!--配置服务bean-->
	<context:component-scan base-package="com.esom.tech.springjpa.service"/>
</beans>

 

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

<?xml version="1.0" encoding="UTF8"?>
<beans xmlns="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-->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
		<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
		<property name="url" value="jdbc:oracle:thin:@171.22.70.28:1521:gbst" />
		<property name="username" value="username" />
		<property name="password" value="password" />
		<property name="initialSize" value="1"/>
		<property name="maxActive" value="2"/>
	</bean>
	
	<!-- Parent Entity Manager : Hibernate 实现 -->
	<bean id="parentEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
		abstract="true">
		<property name="jpaVendorAdapter">
			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
				<property name="database" value="ORACLE" />
				<property name="showSql" value="true" />
				<property name="generateDdl" value="true" />
			</bean>
		</property>
		
	</bean>
	<!-- Entity Manager -->
	<bean id="entityManagerFactory" parent="parentEntityManagerFactory">
		<property name="dataSource" ref="dataSource" />
		<property name="packagesToScan">
			<array>
				<!--entity扫描目录,可多个value节点-->
				<value>com.esom.tech.springjpa.domain</value>				
			</array>
		</property>
	</bean>

	<!-- Transaction Manager -->
	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>
	<tx:advice id="businessTxAdvise" transaction-manager="transactionManager" />
	<aop:config>
		<aop:pointcut id="businessPointcut"
			expression="execution(* com.esom.tech..*.*(..))" />
		<aop:advisor advice-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;

public interface 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
public class UserServiceImpl implements UserService{
	
	@Autowired
	private UserRepository userRepository;

	public boolean 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;

import static 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
public class SpringJpaTest {
	@Autowired
	UserRepository repository;
	@Autowired
	UserService userService;
	User user;
	
	//每个@Test方法都会先执行init()一遍
	@Before	
	public void init(){
		user = new User();
		user.setAge(28);
		user.setFirstName("Lios");
		user.setLastName("Lin");
		user.setUserName("Lios Lin");
	}
	
	// crud方法测试 
	@Test
	public void 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
	public void testMethodQuery() throws Exception {
		repository.save(user);
		List<User> users = repository.findByLastName("Lin");
		assertNotNull(users);
		assertTrue(users.contains(user));
	}

	// named query测试
	@Test
	public void testNameQuery() throws Exception {
		repository.save(user);
		List<User> users = repository.findByFirstNameOrLastName("Lin");
		assertTrue(users.contains(user));
	}
	
	// criteria query测试
	@Test
	public void 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
	public void testOtherQuery() throws Exception {
		repository.save(user);
		
		//占位符查询
		List<User> users = repository.findByFirstNameAndLastName(user.getFirstName(), user.getLastName());
		assertTrue(users.contains(user));
	}
	
	// service测试
	@Test
	public void 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中的办法:

public interface 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: 方法级别的查询,针对 findByfindreadByreadgetBy等

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


 
 

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") 
 public int 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驱动就添加到工程中了。

 

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

猜你喜欢

转载自mixo44.iteye.com/blog/1797079