JPA是什麼
JPA(Java Persistence API)是Sun官方提出的Java持久化規範,它爲Java開發人員提供了一種對象/關聯映射工具 來管理Java應用中的關係數據.它包括以下幾方面的內容:
1.ORM映射 支持xml和註解方式建立實體與表之間的映射.
2.Java持久化API 定義了一些常用的CRUD接口,我們只需直接調用,而不需要考慮底層JDBC和SQL的細節.
3.JPQL查詢語言 這是持久化操作中很重要的一個方面,通過面向對象而非面向數據庫的查詢語言查詢數據,避免程序的SQL語句緊密耦合.
在工作中,我們都會用到ORM技術,比如Hibernate,JOOQ等,根據需求的不同,我們會採用不同的ORM框架,當我們需要 更換ORM框架來滿足我們的需求時,由於不同ORM框架的實現,使用方式的區別以及各自爲營,我們往往需要對代碼進行重構.JPA的 出現就是爲了解決這個問題,JPA充分吸收了現有一些ORM框架的優點,具有易於使用,伸縮性強等優點,爲ORM技術提供了一套標準的 接口用來整合不同的ORM框架.
Hibernate對JPA的實現
JPA本身並不做具體的實現,而只是定義了一些接口規範,讓其它ORM來具體的實現這些接口,就目前來說,對JPA規範實現最好的就是 Hibernate了.這裏提一下Mybatis,Mybatis並沒有實現JPA規範,它本身也不能算做一個真正的ORM框架.
Spring Data JPA是什麼
Spring Data JPA只是Spring Data框架的一個模塊,可以極大的簡化JPA的使用,Spring Data JPA強大的地方還在於能夠簡化我們 對持久層業務邏輯的開發,通過規範持久層方法的名稱,通過名稱來判斷需要實現什麼業務邏輯,我們機會可以在不寫一句sql,不做任何dao層 邏輯的情況下完成我們絕大部分的開發,當然,對於一些複雜的,性能要求高的查詢,Spring Data JPA一樣支持我們使用原生的sql.
在這裏我們不過多的去介紹JPA以及Spring Data JPA,主要還是與SpringBoot集成的一些細節以及示例.
引入依賴
1
2
3
4
5
|
<!--
https: //mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa
--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> |
我們引入這個依賴後,發現也引入了Hibernate的包,這是現在一種默認的做法,Hibernate已經被作爲JPA規範的最好實現了,這裏就不介紹Druid數據源的 配置了,大家可以看另外一篇XXXX.
配置我們的數據源以及JPA(Hibernate)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#配置模板 #https: //docs.spring.io/spring-boot/docs/1.4.0.RELEASE/reference/html/common-application-properties.html #數據源 spring.datasource.druid.write.url=jdbc:mysql: //localhost:3306/jpa spring.datasource.druid.write.username=root spring.datasource.druid.write.password= 1 spring.datasource.druid.write.driver- class -name=com.mysql.jdbc.Driver spring.datasource.druid.read.url=jdbc:mysql: //localhost:3306/jpa spring.datasource.druid.read.username=root spring.datasource.druid.read.password= 1 spring.datasource.druid.read.driver- class -name=com.mysql.jdbc.Driver #JPA
(JpaBaseConfiguration, HibernateJpaAutoConfiguration) spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect spring.jpa.database=mysql spring.jpa.generate-ddl= true #就是hibernate.hbm2ddl.auto,具體說明可以看README spring.jpa.hibernate.ddl-auto=update #通過方法名解析sql的策略,具體說明可以看README,這裏就不配置了 spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultComponentSafeNamingStrategy spring.jpa.show-sql= true #spring.jpa.properties.* #spring.jpa.properties.hibernate.hbm2ddl.auto=update #spring.jpa.properties.hibernate.show_sql= true #spring.jpa.properties.hibernate.use- new -id-generator-mappings= true |
druid數據源注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@Configuration public class DruidDataSourceConfig
{ /** *
DataSource 配置 *
@return */ @ConfigurationProperties (prefix
= "spring.datasource.druid.read" ) @Bean (name
= "readDruidDataSource" ) public DataSource
readDruidDataSource() { return new DruidDataSource(); } /** *
DataSource 配置 *
@return */ @ConfigurationProperties (prefix
= "spring.datasource.druid.write" ) @Bean (name
= "writeDruidDataSource" ) @Primary public DataSource
writeDruidDataSource() { return new DruidDataSource(); } } |
EntityManagerFactory實例注入
EntityManagerFactory類似於Hibernate的SessionFactory,mybatis的SqlSessionFactory 總之,在執行操作之前,我們總要獲取一個EntityManager,這就類似於Hibernate的Session, mybatis的sqlSession. 注入EntityManagerFactory有兩種方式,一種是直接注入EntityManagerFactory,另一種是通過 LocalContainerEntityManagerFactoryBean來間接注入.雖說這兩種方法都是基於 LocalContainerEntityManagerFactoryBean的,但是在配置上還是有一些區別.
1.直接注入EntityManagerFactory
配置:通過spring.jpa.properties.*來配置Hibernate的屬性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
spring.jpa.properties.hibernate.hbm2ddl.auto=update spring.jpa.properties.hibernate.show_sql= true spring.jpa.properties.hibernate.use- new -id-generator-mappings= true @Configuration @EnableJpaRepositories (value
= "com.lc.springBoot.jpa.repository" , entityManagerFactoryRef
= "writeEntityManagerFactory" , transactionManagerRef= "writeTransactionManager" ) public class WriteDataSourceConfig
{ @Autowired JpaProperties
jpaProperties; @Autowired @Qualifier ( "writeDruidDataSource" ) private DataSource
writeDruidDataSource; /** *
EntityManagerFactory類似於Hibernate的SessionFactory,mybatis的SqlSessionFactory *
總之,在執行操作之前,我們總要獲取一個EntityManager,這就類似於Hibernate的Session, *
mybatis的sqlSession. *
@return */ @Bean (name
= "writeEntityManagerFactory" ) @Primary public EntityManagerFactory
writeEntityManagerFactory() { HibernateJpaVendorAdapter
vendorAdapter = new HibernateJpaVendorAdapter(); LocalContainerEntityManagerFactoryBean
factory = new LocalContainerEntityManagerFactoryBean(); factory.setJpaVendorAdapter(vendorAdapter); factory.setPackagesToScan( "com.lc.springBoot.jpa.entity" ); factory.setDataSource(writeDruidDataSource); //數據源 factory.setJpaPropertyMap(jpaProperties.getProperties()); factory.afterPropertiesSet(); //在完成了其它所有相關的配置加載以及屬性設置後,才初始化 return factory.getObject(); } /** *
配置事物管理器 *
@return */ @Bean (name
= "writeTransactionManager" ) @Primary public PlatformTransactionManager
writeTransactionManager() { JpaTransactionManager
jpaTransactionManager = new JpaTransactionManager(); jpaTransactionManager.setEntityManagerFactory( this .writeEntityManagerFactory()); return jpaTransactionManager; } } |
2.先注入LocalContainerEntityManagerFactoryBean,再獲取EntityManagerFactory
配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect spring.jpa.database=mysql spring.jpa.generate-ddl= true #就是hibernate.hbm2ddl.auto,具體說明可以看README spring.jpa.hibernate.ddl-auto=update #通過方法名解析sql的策略,具體說明可以看README,這裏就不配置了 spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultComponentSafeNamingStrategy spring.jpa.show-sql= true @Configuration @EnableJpaRepositories (value
= "com.lc.springBoot.jpa.repository" , entityManagerFactoryRef
= "writeEntityManagerFactory" , transactionManagerRef
= "writeTransactionManager" ) public class WriteDataSourceConfig1
{ @Autowired JpaProperties
jpaProperties; @Autowired @Qualifier ( "writeDruidDataSource" ) private DataSource
writeDruidDataSource; /** *
我們通過LocalContainerEntityManagerFactoryBean來獲取EntityManagerFactory實例 *
@return */ @Bean (name
= "writeEntityManagerFactoryBean" ) @Primary public LocalContainerEntityManagerFactoryBean
writeEntityManagerFactoryBean(EntityManagerFactoryBuilder builder) { return builder .dataSource(writeDruidDataSource) .properties(jpaProperties.getProperties()) .packages( "com.lc.springBoot.jpa.entity" ) //設置實體類所在位置 .persistenceUnit( "writePersistenceUnit" ) .build(); //.getObject();//不要在這裏直接獲取EntityManagerFactory } /** *
EntityManagerFactory類似於Hibernate的SessionFactory,mybatis的SqlSessionFactory *
總之,在執行操作之前,我們總要獲取一個EntityManager,這就類似於Hibernate的Session, *
mybatis的sqlSession. *
@param builder *
@return */ @Bean (name
= "writeEntityManagerFactory" ) @Primary public EntityManagerFactory
writeEntityManagerFactory(EntityManagerFactoryBuilder builder) { return this .writeEntityManagerFactoryBean(builder).getObject(); } /** *
配置事物管理器 *
@return */ @Bean (name
= "writeTransactionManager" ) @Primary public PlatformTransactionManager
writeTransactionManager(EntityManagerFactoryBuilder builder) { return new JpaTransactionManager(writeEntityManagerFactory(builder)); } } |
對於這個配置
1
2
3
4
5
6
7
8
9
10
11
|
@Bean (name
= "writeEntityManagerFactoryBean" ) @Primary public LocalContainerEntityManagerFactoryBean
writeEntityManagerFactoryBean(EntityManagerFactoryBuilder builder) { return builder .dataSource(writeDruidDataSource) .properties(jpaProperties.getProperties()) .packages( "com.lc.springBoot.jpa.entity" ) //設置實體類所在位置 .persistenceUnit( "writePersistenceUnit" ) .build(); //.getObject();//不要在這裏直接獲取EntityManagerFactory } |
getObject()方法可以獲取到EntityManagerFactory的實例,看似跟第一種沒有什麼區別,但是我們不能直接用 getObject(),不然會獲取不到,報空指針異常.
讀寫分離配置
自定義注入AbstractRoutingDataSource
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@Configuration public class DataSourceConfig
{ private final static
String WRITE_DATASOURCE_KEY = "writeDruidDataSource" ; private final static
String READ_DATASOURCE_KEY = "readDruidDataSource" ; /** *
注入AbstractRoutingDataSource *
@param readDruidDataSource *
@param writeDruidDataSource *
@return *
@throws Exception */ @Bean public AbstractRoutingDataSource
routingDataSource( @Qualifier (READ_DATASOURCE_KEY)
DataSource readDruidDataSource, @Qualifier (WRITE_DATASOURCE_KEY)
DataSource writeDruidDataSource ) throws Exception
{ DynamicDataSource
dataSource = new DynamicDataSource(); Map<Object,
Object> targetDataSources = new HashMap(); targetDataSources.put(WRITE_DATASOURCE_KEY,
writeDruidDataSource); targetDataSources.put(READ_DATASOURCE_KEY,
readDruidDataSource); dataSource.setTargetDataSources(targetDataSources); dataSource.setDefaultTargetDataSource(writeDruidDataSource); return dataSource; } } |
自定義註解
1
2
3
4
5
6
|
@Target ({ElementType.METHOD,
ElementType.TYPE}) @Retention (RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource
{ String
dataSource() default "" ; //數據源 } |
使用ThreadLocal使數據源與線程綁定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class DynamicDataSourceHolder
{ //使用ThreadLocal把數據源與當前線程綁定 private static final
ThreadLocal<String> dataSources = new ThreadLocal<String>(); public static void
setDataSource(String dataSourceName) { dataSources.set(dataSourceName); } public static String
getDataSource() { return (String)
dataSources.get(); } public static void
clearDataSource() { dataSources.remove(); } } public class DynamicDataSource
extends AbstractRoutingDataSource
{ @Override protected Object
determineCurrentLookupKey() { //可以做一個簡單的負載均衡策略 String
lookupKey = DynamicDataSourceHolder.getDataSource(); System.out.println( "------------lookupKey---------" +lookupKey); return lookupKey; } } |
定義切面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@Aspect @Component public class DynamicDataSourceAspect
{ @Around ( "execution(public
* com.lc.springBoot.jpa.service..*.*(..))" ) public Object
around(ProceedingJoinPoint pjp) throws Throwable
{ MethodSignature
methodSignature = (MethodSignature) pjp.getSignature(); Method
targetMethod = methodSignature.getMethod(); if (targetMethod.isAnnotationPresent(TargetDataSource. class ))
{ String
targetDataSource = targetMethod.getAnnotation(TargetDataSource. class ).dataSource(); System.out.println( "----------數據源是:" +
targetDataSource + "------" ); DynamicDataSourceHolder.setDataSource(targetDataSource); } Object
result = pjp.proceed(); //執行方法 DynamicDataSourceHolder.clearDataSource(); return result; } } |