jdbc-jdbcTemplate-hibernate-jpa-springDataJpa系列(二)

#1 前面的文章索引java

詳見第一篇文章jdbc-jdbcTemplate-hibernate-jpa-springDataJpa系列(一)mysql

這裏繼續第一篇文章的內容,開始介紹jpagit

#2 jpa原生開發和事務的使用spring

##2.1 jpa的來源sql

因爲各類orm框架層出不窮,爲了統一你們,就出現了jpa這一層接口規範,不一樣的orm框架都去實現這一規範,而後咱們就只關心使用jpa的編程接口來進行編程,不用再關係底層到底使用的是那種orm框架,同時也很容易切換底層所使用的orm框架數據庫

##2.2 項目地址編程

##2.3 jpa的配置session

  • 第一步:就是jpa的核心配置文件 persistence.xml框架

    默認狀況下該配置文件存放的位置是classpath路徑下 META-INF/persistence.xml。配置文件的內容以下:ide

    <persistence-unit name="test" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <properties>
            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
            <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/test" />
            <property name="hibernate.connection.username" value="root" />
            <property name="hibernate.connection.password" value="ligang" />
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.hbm2ddl.auto" value="update" />
        </properties> 
    </persistence-unit>

    咱們能夠看到這個核心配置文件其實就是指明瞭jpa底層所使用的是何種orm框架,這裏稱做爲provider。而後properties裏面的內容,其實都是爲provider準備的一些配置信息。

  • 第二步: 根據核心配置文件建立EntityManagerFactory

    EntityManagerFactory entityManagerFactory=Persistence.createEntityManagerFactory("test");
  • 第三步:根據EntityManagerFactory建立出EntityManager

    EntityManager entityManager=entityManagerFactory.createEntityManager();
  • 第四步:根據EntityManager對實體entity進行增刪改查

    entityManager.persist(user)

##2.4 jpa的幾個重要對象說明

建議與Hibenrate的幾個重要的原生對象進行對比,地址Hibernate的原生xml方式開發和事務的使用

  • PersistenceProvider接口:

    根據持久化單元名稱和配置參數建立出EntityManagerFactory,接口定義以下:

    public interface PersistenceProvider {
    	public EntityManagerFactory createEntityManagerFactory(String emName, Map map);
    	public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map);
    	//略
    }

    jpa僅僅是一層接口規範,不一樣的底層的實現者提供各自的provider。如hibernate提供的provider實現是org.hibernate.jpa.HibernatePersistenceProvider

  • EntityManagerFactory接口:

    就是EntityManager的工廠。其實能夠相似於Hibernate中的SessionFactory。對於Hibernate來講,其實它就是對SessionFactory的封裝,Hibernate實現的EntityManagerFactory是EntityManagerFactoryImpl,源碼以下:

    public class EntityManagerFactoryImpl implements HibernateEntityManagerFactory {
    
    	private final transient SessionFactoryImpl sessionFactory;
    	//略
    }
  • EntityManager接口:

    可以對實體entity進行增刪改查,其實能夠相似於Hibernate中的Session。對於Hibernate來講,其實它就是對Session的封裝。Hibernate實現的EntityManager是EntityManagerImpl,源碼以下:

    public class EntityManagerImpl extends AbstractEntityManagerImpl implements SessionOwner {
    
    	protected Session session;
    	//略
    }
  • EntityTransaction接口:

    jpa定義的事務接口,其實能夠相似於Hibernate原生的的Transaction接口。對於Hibernate來講,其實它就是對Transaction的封裝。Hibernate實現的EntityTransaction是TransactionImpl,源碼以下:

    public class TransactionImpl implements EntityTransaction {
    
    	private Transaction tx;
    	//略
    }

##2.5 jpa的使用案例

@Repository
public class JpaDao {
	private EntityManagerFactory entityManagerFactory;
	public JpaDao(){
		entityManagerFactory=Persistence.createEntityManagerFactory("test");
	}
	public EntityManager getEntityManager(){
		return entityManagerFactory.createEntityManager();
	}
}

@Repository
public class UserDao {
	@Autowired
	private JpaDao jpaDao;
	public void save(User user){
		EntityManager entityManager=jpaDao.getEntityManager();
		EntityTransaction tx=null;
		try {
			tx=entityManager.getTransaction();
			tx.begin();
			entityManager.persist(user);
			tx.commit();
		} catch (Exception e) {
			if(tx!=null){
				tx.rollback();
			}
		}finally{
			entityManager.close();
		}
	}
}

咱們能夠看到,上述的save過程和Hibernate的過程很是類似,只不過把Hibernate的那一套對象換成了對應的jpa對象。

建議與Hibenrate的使用過程進行對比,地址Hibernate的原生xml方式開發和事務的使用

#3 jpa與spring的集成

上述jpa原生方式,尚未使用dataSource,爲了引入dataSource來更好的管理數據庫鏈接,爲了簡化jpa的配置,同時能夠去掉jpa的核心配置文件,spring針對原生的jpa作了本身的集成工做。

##3.1 項目地址

##3.2 配置

  • 第一步:配置數據庫鏈接池dataSource

    <bean id="dataSource"   
        class="com.mchange.v2.c3p0.ComboPooledDataSource"   
        destroy-method="close">   
        <property name="driverClass">   
            <value>${jdbc.driverClass}</value>   
        </property>   
        <property name="jdbcUrl">   
            <value>${jdbc.url}</value>   
        </property>   
        <property name="user">   
            <value>${jdbc.user}</value>   
        </property>   
        <property name="password">   
            <value>${jdbc.password}</value>   
        </property>   
        <!--鏈接池中保留的最小鏈接數。-->   
        <property name="minPoolSize">   
            <value>${jdbc.minPoolSize}</value>   
        </property>   
        <!--鏈接池中保留的最大鏈接數。Default: 15 -->   
        <property name="maxPoolSize">   
            <value>${jdbc.maxPoolSize}</value>   
        </property>   
        <!--初始化時獲取的鏈接數,取值應在minPoolSize與maxPoolSize之間。Default: 3 -->   
        <property name="initialPoolSize">   
            <value>${jdbc.initialPoolSize}</value>   
        </property>   
    </bean>
  • 第二步:配置entityManagerFactory

    <bean id="entityManagerFactory"    	class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    	<property name="dataSource" ref="dataSource"/>
    	<property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
    	<property name="packagesToScan" value="com.demo.entity"/>
    </bean>

    其中的jpaVendorAdapter實際上是收集配置參數,而後告訴LocalContainerEntityManagerFactoryBean所使用的PersistenceProvider,稍後詳細分析,配置以下:

    <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <property name="database" value="MYSQL"/>
        <property name="showSql" value="true"/>
        <property name="generateDdl" value="true"/>
        <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
    </bean>
  • 第三步:配置事務管理器JpaTransactionManager

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    	<property name="entityManagerFactory" ref="entityManagerFactory"></property>
    </bean>

    JpaTransactionManager是依賴於entityManagerFactory的

  • 第四步:啓動@Transactional註解的處理器

    <tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>

    處理器依賴於transactionManager的

##3.3 使用案例

@Repository
public class UserDao {

	@PersistenceContext
	private EntityManager entityManager;
	
	@Transactional
	public void save(User user){
		entityManager.persist(user);
	}
}

使用@PersistenceContext註解來獲取entityManagerFactory建立的EntityManager對象,而後使用EntityManager進行增刪改查

##3.4 原理分析

###3.4.1 如何建立EntityManagerFactory

咱們能夠看到spring是使用了一個工廠bean LocalContainerEntityManagerFactoryBean來建立entityManagerFactory。雖然配置的是一個工廠bean,可是容器在根據id來獲取bean的時候,返回的是該工廠bean所建立的實體,即LocalContainerEntityManagerFactoryBean所建立的EntityManagerFactory。

spring建立EntityManagerFactory有2中方式,以下圖所示: spring建立EntityManagerFactory有2中方式

  • LocalEntityManagerFactoryBean

    它分紅2種狀況來建立

    • 方式1:當LocalEntityManagerFactoryBean自己指定了PersistenceProvider,就使用該PersistenceProvider來建立EntityManagerFactory,詳見上文的PersistenceProvider接口定義

    • 方式2:使用上文jpa原生方式的:

      EntityManagerFactory entityManagerFactory=Persistence.createEntityManagerFactory("test");

    LocalEntityManagerFactoryBean的源碼以下:

    public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryBean {
    
    	@Override
    	protected EntityManagerFactory createNativeEntityManagerFactory() 
    			throws PersistenceException {
    		PersistenceProvider provider = getPersistenceProvider();
    		if (provider != null) {
    			// Create EntityManagerFactory directly through PersistenceProvider.
    			EntityManagerFactory emf = provider.createEntityManagerFactory
    				(getPersistenceUnitName(), getJpaPropertyMap());
    			return emf;
    		}
    		else {
    			// Let JPA perform its standard PersistenceProvider autodetection.
    			return Persistence.createEntityManagerFactory(
    				getPersistenceUnitName(), getJpaPropertyMap());
    		}
    	}
    	//略了部份內容
    }

    咱們再詳細瞭解下下面的這個過程:

    EntityManagerFactory entityManagerFactory=Persistence.createEntityManagerFactory("test");

    它其實也是先獲取全部的PersistenceProvider,而後遍歷PersistenceProvider來建立EntityManagerFactory,源碼以下:

    public static EntityManagerFactory createEntityManagerFactory(String 			
    				persistenceUnitName, Map properties) {
    	EntityManagerFactory emf = null;
    	List<PersistenceProvider> providers = getProviders();
    	for ( PersistenceProvider provider : providers ) {
    		emf = provider.createEntityManagerFactory( persistenceUnitName, properties );
    		if ( emf != null ) {
    			break;
    		}
    	}
    	if ( emf == null ) {
    		throw new PersistenceException( "No Persistence provider 
    			for EntityManager named " + persistenceUnitName );
    	}
    	return emf;
    }

    那它是如何來獲取全部的PersistenceProvider的呢?

    這裏就用到了<font color="red">java的SPI機制(Service Provider Interfaces)</font>。

    以下簡單說明下,詳細內容能夠自行搜索:

    • jpa定義了PersistenceProvider接口
    • Hibernate要實現這個接口,在hibernate-entitymanager這個jar包中,在META-INF/services文件夾下,會有一個以PersistenceProvider接口全稱命名的文件,以下圖所示:

    PersistenceProvider的SPI機制

    文件裏面的內容就是該接口對應的實現類,內容以下:

    org.hibernate.jpa.HibernatePersistenceProvider
    # The deprecated provider, logs warnings when used.
    org.hibernate.ejb.HibernatePersistence

    這就很容易方便jpa來尋找PersistenceProvider全部的實現類

  • LocalContainerEntityManagerFactoryBean

    它建立一個PersistenceProvider須要如下幾個重要的屬性元素

    • jpaVendorAdapter

      它的主要做用就是收集一些配置信息,而且提供一個PersistenceProvider。接口定義以下:

      public interface JpaVendorAdapter {
      	PersistenceProvider getPersistenceProvider();
      	Map<String, ?> getJpaPropertyMap();
      	//略
      }

      以HibernateJpaVendorAdapter爲例,在它初始化的時候就會建立出HibernatePersistenceProvider,以下:

      public class HibernateJpaVendorAdapter extends AbstractJpaVendorAdapter
      	private final PersistenceProvider persistenceProvider;
      	public HibernateJpaVendorAdapter() {
      		PersistenceProvider providerToUse;
      		try {
      			ClassLoader cl = HibernateJpaVendorAdapter.class.getClassLoader();
      			Class<?> hibernatePersistenceProviderClass = cl.loadClass
      				("org.hibernate.jpa.HibernatePersistenceProvider");
      			providerToUse = (PersistenceProvider) hibernatePersistenceProviderClass.newInstance();
      		}
      		this.persistenceProvider = providerToUse;
      		//略
      	}
      }
    • PersistenceProvider :

      它的來歷至少有兩種方式:

      • 在配置LocalContainerEntityManagerFactoryBean的時候,直接配置一個PersistenceProvider
      • 若是沒有進行上述配置,則使用上述的jpaVendorAdapter指定的PersistenceProvider
    • dataSource

      再也不像原生的jpa那樣直接使用核心配置文件中的鏈接信息(這些鏈接信息是配置給PersistenceProvider的)

    • packagesToScan等信息

      用於指定註解式實體所在的包路徑,和dataSource同樣都做爲配置信息,最終都反應在PersistenceProvider建立的EntityManagerFactory上了

###3.4.2 如何獲取EntityManager

咱們看到在例子中,是經過使用@PersistenceContext來獲取一個EntityManager的,咱們知道這個EntityManager就是經過咱們配置的上述EntityManagerFactory來建立的,但具體是一個什麼過程呢?

忽然發現這一塊源碼內容也好多,主要是PersistenceAnnotationBeanPostProcessor這個處理器在處理

@PersistenceContext註解和咱們經常使用的@Autowired是相似的,他們都是實現依賴注入的,內容仍是不少,因此打算以後另開一篇博客,單獨講解這類依賴注入的註解

至此咱們就大體瞭解jpa與Spring是怎麼集成的,總之仍是經過PersistenceProvider和配置信息來建立出EntityManagerFactory。

###3.4.3 事務過程是怎麼實現的

過程比較複雜,總之原理仍是使用ThreadLocal機制

@PersistenceContext
private EntityManager entityManager;

@Transactional
public void save(User user){
	entityManager.persist(user);
	throw new RuntimeException();
}
  • 1 在執行save方法以前,使用了事務管理器JpaTransactionManager執行了開啓事務的操做

    • 1.1 建立了一個EntityManager對象,以Hibernate來講就是建立了EntityManagerImpl(同時內部建立了Session和TransactionImpl),而後經過TransactionImpl開啓事務
    • 1.2 將建立的EntityManagerImpl對象綁定到當前線程
  • 2 經過注入進來的entityManager來保存user

    這個注入進來的entityManager僅僅是一個空殼子,是一個代理對象,它會獲取當前線程綁定的上述EntityManagerImpl對象,來實現保存user

  • 3 一旦出現異常,則使用1.1步驟中建立的EntityManagerImpl中的事務TransactionImpl來實現回滾

總之,保證了業務代碼和事務代碼使用的是同一個EntityManager對象對象,因此能夠正常回滾。 總之使用@Transactional註解式的事務,總要使用ThreadLocal模式來保證業務代碼和事務代碼中的使用的connection是一致的這一原則。

#4 多數據源下jpa與spring集成開發和事務的使用

##4.1 項目地址

##4.2 配置

###4.2.1 配置兩個數據庫鏈接池dataSource

<bean id="dataSource1"   
    class="com.mchange.v2.c3p0.ComboPooledDataSource"   
    destroy-method="close">   
    <property name="driverClass">   
        <value>${jdbc1.driverClass}</value>   
    </property>   
    <property name="jdbcUrl">   
        <value>${jdbc1.url}</value>   
    </property>   
    //略,見項目中的配置
</bean>

 <!-- 配置數據源 -->
 <bean id="dataSource2"   
    class="com.mchange.v2.c3p0.ComboPooledDataSource"   
    destroy-method="close">   
    <property name="driverClass">   
        <value>${jdbc2.driverClass}</value>   
    </property>   
    <property name="jdbcUrl">   
        <value>${jdbc2.url}</value>   
    </property>   
    //略,見項目中的配置 
</bean>

記得根據配置建立相應的數據庫和表

###4.2.2 配置2個entityManagerFactory

<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    <property name="database" value="MYSQL"/>
    <property name="showSql" value="true"/>
    <property name="generateDdl" value="true"/>
    <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
</bean>

<bean id="entityManagerFactory1" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
	<property name="dataSource" ref="dataSource1"/>
	<property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
	<property name="packagesToScan" value="com.demo.entity"/>
</bean>

<bean id="entityManagerFactory2" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
	<property name="dataSource" ref="dataSource2"/>
	<property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
	<property name="packagesToScan" value="com.demo.entity"/>
	<property name="persistenceUnitName" value="test2"/>
</bean>

這兩個entityManagerFactory使用不一樣的dataSource,各自有一個persistenceUnitName名字(持久化單元的名字),分別叫"test1"和"test2"。在上文中,並無配置persistenceUnitName,採用默認值"default"

###4.2.3 配置2個事務管理器

<bean id="transactionManager1" class="org.springframework.orm.jpa.JpaTransactionManager">
	<property name="entityManagerFactory" ref="entityManagerFactory1"></property>
</bean>

<bean id="transactionManager2" class="org.springframework.orm.jpa.JpaTransactionManager">
	<property name="entityManagerFactory" ref="entityManagerFactory2"></property>
</bean>

###4.2.4 開啓@Transactional的處理器

<tx:annotation-driven proxy-target-class="true"/>

##4.3 使用方式

@Repository
public class UserDao {

	@PersistenceContext(unitName="test1")
	private EntityManager entityManager;
	@PersistenceContext(unitName="test2")
	private EntityManager entityManagerTest2;
	
	@Transactional("transactionManager1")
	public void save(User user){
		entityManager.persist(user);
	}
	@Transactional("transactionManager2")
	public void save2(User user){
		entityManagerTest2.persist(user);
	}
}

@PersistenceContext(unitName="test1")表示使用persistenceUnitName="test1"的entityManagerFactory來建立EntityManager,同理@PersistenceContext(unitName="test2")也同樣。

若是有多個entityManagerFactory,可是隻使用@PersistenceContext沒有指定unitName,則會報錯,spring不知道該選擇哪個,因此須要指定unitName的名字

@Transactional("transactionManager1")表示使用id="transactionManager1"的JpaTransactionManager來做爲事務管理器 同理@Transactional("transactionManager2")也同樣。

多數據源就再也不詳細說明了

#5 spring-data-jpa的開發和事務的使用

##5.1 背景

引用IBM的一篇文章使用 Spring Data JPA 簡化 JPA 開發

Spring 對 JPA 的支持已經很是強大,開發者只需關心核心業務邏輯的實現代碼,無需過多關注 EntityManager 的建立、事務處理等 JPA 相關的處理,這基本上也是做爲一個開發框架而言所能作到的極限了。然而,Spring 開發小組並無止步,他們再接再礪,於最近推出了 Spring Data JPA 框架,主要針對的就是 Spring 惟一沒有簡化到的業務邏輯代碼,至此,開發者連僅剩的實現持久層業務邏輯的工做都省了,惟一要作的,就只是聲明持久層的接口,其餘都交給 Spring Data JPA 來幫你完成

至此,讀者可能會存在一個疑問,框架怎麼可能代替開發者實現業務邏輯呢?畢竟,每個應用的持久層業務甚至領域對象都不盡相同,框架是怎麼作到的呢?其實這背後的思想並不複雜,好比,當你看到 UserDao.findUserById() 這樣一個方法聲明,大體應該能判斷出這是根據給定條件的 ID 查詢出知足條件的 User 對象。Spring Data JPA 作的即是規範方法的名字,根據符合規範的名字來肯定方法須要實現什麼樣的邏輯

這篇文章的內容已經很詳細了,這裏就僅僅詳細地列出配置,真正跑起來

##5.2 配置

有了前面的基礎後,就很是簡單了

###5.2.1 配置dataSource

<bean id="dataSource"   
    class="com.mchange.v2.c3p0.ComboPooledDataSource"   
    destroy-method="close">   
    <property name="driverClass">   
        <value>${jdbc.driverClass}</value>   
    </property>   
    <property name="jdbcUrl">   
        <value>${jdbc.url}</value>   
    </property>   
    //略   
</bean>

###5.2.2 配置entityManagerFactory

<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    <property name="database" value="MYSQL"/>
    <property name="showSql" value="true"/>
    <property name="generateDdl" value="true"/>
    <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
</bean>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
	<property name="dataSource" ref="dataSource"/>
	<property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
	<property name="packagesToScan" value="com.demo.entity"/>
</bean>

###5.2.3 配置transactionManager

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
	<property name="entityManagerFactory" ref="entityManagerFactory"></property>
</bean>

###5.2.4 開啓@Transactional的處理器

<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>

###5.2.5 配置要掃描的dao的路徑

<jpa:repositories base-package="com.demo.dao"/>

使用jpa命名空間的元素,須要加入以下jpa的約束配置:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans" 
	xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	//略
	xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/data/jpa
    http://www.springframework.org/schema/data/jpa/spring-jpa-1.2.xsd">

咱們以前須要本身手寫一個UserDao,本身實現相應的方法,注入EntityManager,而後進行增刪改查,如今不須要了,只需定義一個接口便可

public interface UserDao extends CrudRepository<User,Long>{

}

CrudRepository<User,Long> 類型中的前者User表示User實體,後者Long表示User實體的主鍵類型

##5.3 使用過程

咱們只需定義上述一個接口,便可在別的地方注入使用UserDao,來進行增刪改查,以下:

@Autowired
private UserDao userDao;

@Test
public void testSaveUser(){
	User user=new User();
	user.setName("王五");
	user.setAge(22);
	userDao.save(user);
}

##5.4 spring-data-jpa實現的功能介紹

上面僅僅是一個簡單的使用例子,更多複雜的例子,見IBM的一篇文章使用 Spring Data JPA 簡化 JPA 開發

相關文章
相關標籤/搜索