ORM(object-relational mapping)——对象/关系 映射。
Spring对多个ORM框架提供了支持。下面分别介绍Spring对Hibernate和JPA(Java持久化API,java Persistence API)的支持。
maven:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-ormartifactId>
<version>4.3.8.RELEASEversion>
dependency>
使用Hibernate所需的主要接口是org.hibernate.Session。Session接口提供了基本的数据访问功能。获取Hibernate Session对象的标准方式是借助于Hibernate Session Factory接口的实现类。SessionFactory主要负责Hibernate Session的打开、关闭以及管理。
从3.1版本开始,Spring提供了三个Session工厂bean供我们选择:
至于选择哪一个Session工厂,取决于使用哪一个版本的Hibernate以及使用XML还是使用注解定义对象-数据库之间的映射关系。XML——LocalSessionFactoryBean
,注解——AnnotationSessionFactoryBean。
org.springframework.orm.hibernate4.LocalSessionFactoryBean
类似于前俩个的结合体,能够支持基于XML的映射和基于注解的映射。
/**
* org.springframework.orm.hibernate3.LocalSessionFactoryBean
* @param dataSource
* @return
*/
@Bean
@Autowired
public LocalSessionFactoryBean sessionFactory(DataSource dataSource){
LocalSessionFactoryBean sfb = new LocalSessionFactoryBean();
sfb.setDataSource(dataSource);
sfb.setMappingResources(new String[] {"test.hnm.xml"});//设置Hibernate映射文件
Properties prop = new Properties();
prop.setProperty("dialect", "org.hibernate.dialect.MySQLDialect");
sfb.setHibernateProperties(prop);
return sfb;
}
/**
* org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean
* @param dataSource
* @return
*/
@Bean
@Autowired
public AnnotationSessionFactoryBean sessionFactory(DataSource dataSource){
AnnotationSessionFactoryBean bean = new AnnotationSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setPackagesToScan(new String[] {"com.sh.domin"});
Properties prop = new Properties();
prop.setProperty("dialect", "org.hibernate.dialect.MySQLDialect");
bean.setHibernateProperties(prop);
return bean;
}
/**
* org.springframework.orm.hibernate4.LocalSessionFactoryBean
* 基于注解的方式
* @param dataSource
* @return
*/
@Bean
@Autowired
public LocalSessionFactoryBean sessionFactory(DataSource dataSource){
LocalSessionFactoryBean sfb = new LocalSessionFactoryBean();
sfb.setDataSource(dataSource);
//使用PackagesToScan属性告诉Spring要扫描一个或多个包以查找域类,这些类通过主机的方式表明
//要使用Hibernate进行持久化,这些类可以使用的注解包括JPA的@Entity或@MappedSuperclass以及Hibernate的@Entity。
sfb.setPackagesToScan(new String[] {"com.sh.domin"});
Properties properties = new Properties();
properties.setProperty("dialect", "org.hibernate.dialect.MySQLDialect");
sfb.setHibernateProperties(properties);
return sfb;
}
使用上下文Session.
@Repository
public class HibernateRepository {
@Autowired
private SessionFactory sessionFactory;//注入SessionFactory
public Session currentSession(){
return sessionFactory.getCurrentSession();//从SessionFactory众获取当前Session
}
@Transactional
public List list(){
//使用当前Session查询user表中的所有数据方法list集合中
return (List)currentSession().createCriteria(User.class).list();
}
}
实体User:
@Entity()
@Table(name="user")
public class User implements Serializable {
private static final long serialVersionUID = -4869287368065710953L;
@Id
private int id;
private String username;
private String password;
private int sex;
@Column(name="id",nullable=false)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Column(name="username")
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Column(name="password")
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Column(name="sex")
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
}
@Repository是Spring的另一种构造性注解,它能够像其他注解一样被Spring的组件扫描所扫描到,只要这个Repository类在组件扫描所涵盖的包中即可。它还有一项任务就是捕获平台相关的异常,然后使用Spring统一非检查型异常的形式重新抛出。因此,我们需要在Spring应用上下文中添加一个PersistenceExceptionTranslationPostProcessor Bean:
@Bean
public BeanPostProcessor persistenceTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
这是一个Bean后置处理器,它会在所有拥有#Repository注解的类上添加一个通知器,这样就会捕获任何平台相关的异常并以Spring非检查型访问异常的形式重新抛出。
org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
原因:Hibernate4 No Session found for current thread原因
此demo的解决办法:
1.启用TransactionManagement:
@Configuration
@ComponentScan(basePackages={"com.sh.*"},
excludeFilters={
@Filter(type=FilterType.ANNOTATION, value=EnableWebMvc.class)
})
@EnableTransactionManagement
public class RootConfig {
...
}
2.配置Bean:
@Bean
@Autowired
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory){
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory);
return transactionManager;
}
3.访问持久层时加注解@Transactional
@Transactional
public List list(){
//使用当前Session查询user表中的所有数据方法list集合中
return (List)currentSession().createCriteria(User.class).list();
}
Connection was closed in SingleConnectionDataSource
解决办法:设置SuppressClose为true
/**
* 配置数据源
* @return
*/
@Bean
public DataSource dataSource(){
SingleConnectionDataSource dataSource = new SingleConnectionDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/ceshi");
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setSuppressClose(true);
return dataSource;
}
org.hibernate.AnnotationException: No identifier specified for entity异常
原因:在使用Hibernate的映射表的时候Entity类是必须要有主键的,否则就会报这个异常。
解决办法:
主键上加上: @Id
@Column(name = “id”, unique = false, nullable = false)
例如上面User类写的那样。
demo源码:SpringMVC4+Hibernate4 Demo
Java持久化API(Java Persistence API,JPA)诞生在EJB2实体Bean的废墟之上,并成为下一代Java持久化标准。JPA是基于POJO的持久化机制,它从Hibernate和Java数据对象(Java Data Object,JDO)上借鉴了很多理念并加入了Java5注解的特性。
在Spring中使用JPA的第一步是要在Spring应用上下文中将实体类管理器工厂(entity manager factory)按照bean的形式进行配置。
基于JPA的应用程序需要使用EntityManagerFactory的实现类来获取EntityManager实例。JPA定义了两种类型的实体管理器:
对于使用JPA的Spring开发者来说,不管你使用哪种EntityManagerFactory。Spring都负责管理EntityManager。对于应用程序管理类型的实体管理器,Spring承担了应用程序的角色并以透明的方式处理EntityManager。在容器管理的场景下,Spring会担当容器的角色。
这两种实体管理器工厂分别有对应的Spring工厂Bean创建:
配置应用程序管理类型的JPA:
对于应用程序管理类型的实体管理器工厂来说,它绝大部分配置信息来源于一个名为persistence.xml的配置文件。这个文件必须位于类路径下的META-INF目录下。
persistence.xml的作用在于定义一个或多个持久化单元。持久化单元是同一个数据源下的一个或多个持久化类。简单来讲,persistence.xml列出了一个或多个的持久化类以及一些其他的配置如数据源和基于XML的配置文件。
persistence.xml
<persistence version="1.0"
xmlns:persistence="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence persistence_1_0.xsd ">
<persistence-unit name="unitName" transaction-type="JTA">
<description> description>
<provider> provider>
<jta-data-source>java:/MySqlDSjta-data-source>
<non-jta-data-source> non-jta-data-source>
<mapping-file>product.xmlmapping-file>
<jar-file>../lib/model.jarjar-file>
<class>com.domain.Userclass>
<class>com.domain.Productclass>
<exclude-unlisted-classes/>
<properties>
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.show_sql" value="true" />
properties>
persistence-unit>
persistence>
Bean:
/**
* 配置应用程序管理类型的JPA
* @return
*/
@Bean
public LocalEntityManagerFactoryBean entityManagerFactoryBean(){
LocalEntityManagerFactoryBean emfb = new LocalEntityManagerFactoryBean();
emfb.setPersistenceUnitName("unitName");
return emfb;
}
使用容器管理类型的JPA
Bean:
/**
* 配置容器管理类型的JPA
*/
@Bean
@Autowired
public LocalContainerEntityManagerFactoryBean containerEntityManagerFactoryBean(DataSource dataSource,JpaVendorAdapter jpaVendorAdapter){
LocalContainerEntityManagerFactoryBean lcemf = new LocalContainerEntityManagerFactoryBean();
lcemf.setDataSource(dataSource);
//指定使用哪一个厂商的JPA实现
lcemf.setJpaVendorAdapter(jpaVendorAdapter);
//扫描com.jpa.domin包下带有@Entity注解的实体类
lcemf.setPackagesToScan("com.jpa.domin");
return lcemf;
}
jpaVendorAdapter属性用于指明所使用的是哪一个厂商的JPA实现。Spring提供了多个JPA厂商适配器:
这里使用Hibernate作为JPA实现
/**
* 指明使用哪一个厂商的JPA实现
* @return
*/
@Bean
public JpaVendorAdapter jpaVendorAdapter(){
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
//设置数据库
adapter.setDatabase(Database.MYSQL);
adapter.setShowSql(true);
adapter.setGenerateDdl(false);
//设置数据库方言
adapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect");
return adapter;
}
database属性指定的使用的数据库。
Hibernate的JPA适配器支持多种数据库,可以通过database属性配置使用哪一个数据库:
数据库平台 | 属性database的值 |
---|---|
IBM DB2 | DB2 |
Apache Derby | DERBY |
H2 | H2 |
Hypersonic | HSQL |
Informix | INFORMIX |
MySQL | MYSQL |
Oracle | ORACLE |
PostgresQL | POSTGRESQL |
Microsoft SQL Server | SQLSERVER |
Sybase | SYBASE |
@Repository
@Transactional
public class TestRepository {
@PersistenceUnit
private EntityManagerFactory emf;
public User findById(int id){
return this.emf.createEntityManager().find(User.class, id);
}
}
@PersistenceUnit注解会将EntityManagerFactory 注入到Repository中。
这里麻烦的是每个方法都会调用this.emf.createEntityManager().来创建EntityManager。因为EntityManager并不是线程安全的,所以我们不能预先准备好它。但是我们可以借助@PersistenceContext注解为Repository设置EntityManager:
@Repository
@Transactional
public class JpaRepository {
@PersistenceContext
private EntityManager em;
public User getUserById(int id){
return em.find(User.class, id);
}
}
真相是@PersistenceContext并不会真正注入EntityManager——精确来讲是这样的。他没有将真正的EntityManager设置给Repository,而是给了他一个EntityManager的代理。真正的EntityManager是与当前事务相关联的哪一个,如果不存在这样的EntityManager的话,就会创建一个新的。
@Transactional表明这Repository中的持久化方法是在事务上下文中执行。
最后,因为我们没有使用spring 模板,所有要配置异常转换的bean给@Repository注解:
@Bean
public BeanPostProcessor persistenceTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
不过,不论是JPA还是Hibernate,这个是可以省略的,如果你希望在Repository中抛出特定的JPA或Hibernate异常,只需将PersistenceExceptionTranslationPostProcessor省略掉即可,这样原来的异常就会正常的处理。不会转换成spring的异常体系。
本节demo:Spring MVC+JPA Demo
Spring Data 能够让我们之编写Repository接口就可以了,而不需要实现类。
public interface ISpringDataJpaRepository extends JpaRepository<User, Integer>{
}
编写Spring Data JPA Repository的关键在于要从一组接口中挑选一个进行扩展。这里我们扩展了Spring data JPA的JpaRepository。后面会介绍其他几个接口。通过这种方式,JpaRepository进行了参数化,所有它就能知道这是一个用来持久化User对象的Repository,并且User的ID类型为Integer。另外它还会继承18个执行持久化的通用方法。
我们不需要创建ISpringDataJpaRepository 的实现,它的实现是由Spring Data完成的。为了让Spring Data 创建ISpringDataJpaRepository 的实现,我们需要在Spring配置中添加一个元素:
XML配置:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
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/task http://www.springframework.org/schema/task/spring-task-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"
default-lazy-init="true">
<description>SpringJpa配置description>
...
<jpa:repositories base-package="com.jpa.dao" transaction-manager-ref="transactionManager" entity-manager-factory-ref="entityManagerFactory"/>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
bean>
...
bean>
java配置:
@EnableJpaRepositories(basePackages="com.jpa.dao")
public class RootConfig {
...
/**
* JPA事物管理器
* @param emf
* @return
*/
@Bean
@Autowired
public JpaTransactionManager transactionManager(EntityManagerFactory emf){
JpaTransactionManager jtm = new JpaTransactionManager(emf);
return jtm;
}
}
这里我们指定了一个基础包,
会扫描它的基础包来查找扩展自Spring Data JPA Repository接口的所有接口。如果发现了扩展自Repository的接口,它会自动生成(在应用启动的时候)这个接口的实现。
配置好了这些以后就可以使用了,我们之前说过,现在的这个接口已经继承了18个方法,所以我们用其中的一个findone():
@Controller
public class TestController {
@Autowired
private ISpringDataJpaRepository sdjr;
@RequestMapping("findone")
public String findOne(){
User user = this.sdjr.findOne(1);
System.out.println(user.getUsername());
return "index";
}
}
public interface ISpringDataJpaRepository extends JpaRepository<User, Integer>{
User findByUsername(String username);
}
当创建Repository实现的时候,Spring Data会检查Repository接口的所有方法,解析方法的名称,并基于被持久化的对象来试图推测方法的目的。本质上,Spring Data定义了一组小型的领域特定语言(domin-specific language,DSL),在这里,持久化的细节都是通过Repository方法的签名来描述的。
Repository方法是一个由动词、一个可选的主题(subject),关键词By以及一个断言所组成的。在上面那个方法中,动词是find,断言是Username,主题并没有指定,暗含的主题是User。
又比如这个:readUserByFirstnameOrLastname()。
Spring Data 允许在方法名中使用四种动词:get,read,find和count,其中,动词get,read和find是同一的,这三个动词对应的Repository方法都会查询数据并返回对象。而动词count则会返回匹配对象的数量,而不是对象本身。
Repository方法的主题是可选的。它的主要目的是让你在命名方法的时候,有更多的灵活性。
要查询的对象类型是通过如何参数化JpaRepository接口来确定的,而不是方法名称中的主题。
在省略主题的时候,有一种例外情况。如果主题的名称以Distinct开头的话,那么在生成查询的时候会确保所返回结果集中不包含重复记录。断言是方法名称中最为有意思的部分,它指定了限制结果集的属性。
在断言中,会有一个或多个限制结果的条件。每个条件必须引用一个属性,并且还可以指定一种比较操作。如果省略比较操作符的话,那么这暗指是一种相等比较操作。不过,我们也可以选择其他的比较操作,包括如下的种类:
List readByFirstnameIgnoringCaseOrLastnameIgnoringCase(String first, String last);
或者这样,结果是一样的:
List readByFirstnameOrLastnameAllIgnoringCase(String first, String last);
排序:
List readByFirstnameOrLastnameOrderByLastnameAscFirstnameDesc(String first,String last);
可以看到条件部分可以通过And或者Or进行分割。
如下给出了几个符合方法命名约定的方法签名:
List findPetsByBreedIn(List breed)
int countProductsByDisContinuedTrue()
List findByShippingDateBetween(Date start, Date end)
如果所需的数据无法通过方法名称进行恰当的描述,那么我们可以使用@Query注解,为Spring Data提供要执行的查询。
//这里的from User指的是User对象,换成user则无法运行
@Query("select u from User u where u.email like '%qq.com'")
List findAllQQEmailUser();
当Spring Data JPA为Repository接口生成实现的时候,它还会查找名字与接口相同,并且添加了Impl后缀的一个类。如果这个类存在的话,Spring Data JPA将会把它的方法与Spring Data JPA所生成的方法合并在一起。对于我们的接口ISpringDataJpaRepository来说,要查找ISpringDataJpaRepositoryImpl类。
所以,在使用Spring Data JPA的同时,我们也可以使用原始的EntityManager。
这是我们的ISpringDataJpaRepositoryImpl类:
@Transactional
public class ISpringDataJpaRepositoryImpl implements UserSweeper{
@PersistenceContext
private EntityManager em;
public int sexSweep() {
String update = "UPDATE User user set user.sex = 1 WHERE user.sex = 0";
return em.createQuery(update).executeUpdate();
}
}
这里,我们没有实现ISpringDataJpaRepository。ISpringDataJpaRepository将由Spring Data JPA实现。ISpringDataJpaRepositoryImpl实现了另一个接口UserSweeper:
public interface UserSweeper {
int sexSweep();
}
最后,让ISpringDataJpaRepository继承一下UserSweeper,或者只要确保UserSweeper中的方法在ISpringDataJpaRepository中出现,就能把ISpringDataJpaRepositoryImpl中的方法和Spring Data JPA为ISpringDataJpaRepository生成的方法绑定在一起。然后通过ISpringDataJpaRepository来使用它:
public interface ISpringDataJpaRepository extends JpaRepository<User, Integer>,UserSweeper{...}
使用:
@Controller
public class TestController {
@Autowired
private ISpringDataJpaRepository sdjr;
@RequestMapping("sex")
public String countSex(){
int count = this.sdjr.sexSweep();
System.out.println(count);
return "index";
}
}
如前所述,Spring Data JPA将实现类与接口关联起来是基于接口的名称。但是,Impl后缀只是默认的做法,如果你想使用其他后缀的话,只需要在配置@EnableJpaRepositories的时候设置repositoryImplementationPostfix属性即可。下面把后缀改为Helper:
java配置:
@EnableJpaRepositories(basePackages="com.jpa.dao",
repositoryImplementationPostfix="Helper")
XML配置:
<jpa:repositories base-packages="com.jpa.dao"
repository-impl-postfix="Helper" />
本节Demo:Spring Data JPA Demo