採用hibernate的JPA實現,對於簡單的查詢十分方便。而對於複雜查詢咱們也能夠寫SQL來進行復雜的多表鏈接查詢。不少人不喜歡hibernate其實更多的是對其機制的掌握不深,若是認真研究其實現源碼,實際上是一個很快樂的學習過程。各類設計範式的運用也是精彩絕倫。java
這裏主要說下緩存的配置。既然是hibernate,其緩存機制離不開這三種:session級別的緩存、sessionFactory級別的二級緩存以及查詢緩存和帶有集合的緩存。對於jpa的使用,我的建議不要使用關係映射。這樣會致使各類關聯查詢,涉及到延遲加載等機制,會消耗額外的cglib的大量代理工做。尤爲是不少人還使用org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter來實現集合的頁面懶加載。這對http響應要求很高的系統簡直就是災難。spring
使用jpa要放棄hibernate的關係映射部分,保持開發的簡潔和靈活性。sql
先上測試代碼
數據庫
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
56
57
58
59
60
61
62
63
複製代碼 |
package cn.com.egova.easyshare.test.service;
import cn.com.egova.easyshare.common.dto.NavMenuDto;
import cn.com.egova.easyshare.common.entity.Human;
import cn.com.egova.easyshare.common.entity.NavMenu;
import cn.com.egova.easyshare.server.service.IHumanService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:/applicationContext.xml"})
public class HumanServiceTest {
@Autowired
private IHumanService humanService;
@Autowired
private EntityManagerFactory entityManagerFactory;
//一級緩存:內建的session級別的緩存,默認開啓
@Test
public void sessionCacheTest(){
EntityManager entityManager = entityManagerFactory.createEntityManager();
Human human = entityManager.find(Human.class,37);
Human human1 = entityManager.find(Human.class,37);
System.out.println(human == human1?"true":"false");
entityManager.close();
entityManager = entityManagerFactory.createEntityManager();
Human human3 = entityManager.find(Human.class,37);
System.out.println(human == human3?"true":"false");
}
//二級緩存,同一個sessionFactory的不一樣session緩存,默認關閉
public void sessionFactoryCacheTest(){
EntityManager entityManager = entityManagerFactory.createEntityManager();
NavMenu navMenu = entityManager.find(NavMenu.class,1);
entityManager.close();
entityManager = entityManagerFactory.createEntityManager();
NavMenu navMenu2 = entityManager.find(NavMenu.class,1);
entityManager.close();
System.out.println(navMenu == navMenu2?"true":"false");
}
//查詢緩存,前兩種都是根據主鍵ID的緩存策略.
@Test
public void queryCacheTest() throws Exception{
List<NavMenuDto> navMenuDtos = humanService.getMenuList(37);
List<NavMenuDto> navMenuDtos2 = humanService.getMenuList(37);
}
}
複製代碼 |
也就是自建的session級別的緩存,在一個session回話事物中根據主鍵查詢是隻會查詢一次數據庫的。
apache
1
2
3
4
5
6
7
8
複製代碼 |
EntityManager entityManager = entityManagerFactory.createEntityManager();
Human human = entityManager.find(Human.class,37);
Human human1 = entityManager.find(Human.class,37);
System.out.println(human == human1?"true":"false");
entityManager.close();
entityManager = entityManagerFactory.createEntityManager();
Human human3 = entityManager.find(Human.class,37);
System.out.println(human == human3?"true":"false");
複製代碼 |
控制檯打印:
緩存
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
56
57
58
59
60
複製代碼 |
Hibernate:
select
human0_.humanID as humanID1_11_0_,
human0_.activeFlag as activeFlag2_11_0_,
human0_.address as address3_11_0_,
human0_.createDate as createDate4_11_0_,
human0_.description as description5_11_0_,
human0_.displayOrder as displayOrder6_11_0_,
human0_.email as email7_11_0_,
human0_.humanCode as humanCode8_11_0_,
human0_.humanName as humanName9_11_0_,
human0_.humanPassword as humanPassword10_11_0_,
human0_.identifyType as identifyType11_11_0_,
human0_.orgId as orgId12_11_0_,
human0_.tel as tel13_11_0_,
human0_.unitID as unitID14_11_0_,
human0_.userid as userid15_11_0_,
human0_.userroles as userroles16_11_0_,
human0_.zipCode as zipCode17_11_0_
from
tcHuman human0_
where
human0_.humanID=?
true
2016-07-20 14:24:30 INFO main org.hibernate.engine.internal.StatisticalLoggingSessionEventListener - Session Metrics {
399787 nanoseconds spent acquiring 1 JDBC connections;
0 nanoseconds spent releasing 0 JDBC connections;
114082560 nanoseconds spent preparing 1 JDBC statements;
2907307 nanoseconds spent executing 1 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}
Hibernate:
select
human0_.humanID as humanID1_11_0_,
human0_.activeFlag as activeFlag2_11_0_,
human0_.address as address3_11_0_,
human0_.createDate as createDate4_11_0_,
human0_.description as description5_11_0_,
human0_.displayOrder as displayOrder6_11_0_,
human0_.email as email7_11_0_,
human0_.humanCode as humanCode8_11_0_,
human0_.humanName as humanName9_11_0_,
human0_.humanPassword as humanPassword10_11_0_,
human0_.identifyType as identifyType11_11_0_,
human0_.orgId as orgId12_11_0_,
human0_.tel as tel13_11_0_,
human0_.unitID as unitID14_11_0_,
human0_.userid as userid15_11_0_,
human0_.userroles as userroles16_11_0_,
human0_.zipCode as zipCode17_11_0_
from
tcHuman human0_
where
human0_.humanID=?
false
複製代碼 |
執行entityManager.close();
entityManager關閉後會從新執行一次sql查詢bash
二級緩存默認是關閉的,須要配置緩存策略
服務器
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
複製代碼 |
<?xml version="1.0" encoding="UTF-8"?>
<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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:cache="http://www.springframework.org/schema/cache"
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-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-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/cache http://www.springframework.org/schema/cache/spring-cache-3.1.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<context:annotation-config />
<context:component-scan base-package="cn.com.egova.easyshare"/>
<!--配置文件讀取-->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:config.properties</value>
</list>
</property>
</bean>
<bean id="springContextUtil" class="cn.com.egova.easyshare.common.utils.SpringContextUtil"></bean>
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="${db.driverClassName}"></property>
<property name="url" value="${db.url}"></property>
<property name="username" value="${db.username}"></property>
<property name="password" value="${db.password}"></property>
<property name="maxTotal" value="${db.maxTotal}"></property>
<property name="maxIdle" value="${db.maxIdle}"></property>
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="generateDdl" value="false" />
<property name="database" value="ORACLE" />
</bean>
</property>
<!-- 指定Entity實體類包路徑 -->
<property name="packagesToScan">
<value>cn.com.egova.easyshare.common.entity</value>
</property>
<!-- 指定JPA屬性;如Hibernate中指定是否顯示SQL的是否顯示、方言等 -->
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle9iDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.generate_statistics">true</prop>
<prop key="hibernate.hbm2ddl.auto">none</prop>
<!-- 配置二級緩存 -->
<prop key="hibernate.cache.use_second_level_cache">true</prop>
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
<!-- 開啓查詢緩存 -->
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.cache.provider_configuration">classpath:ehcache.xml</prop>
</props>
</property>
</bean>
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<!-- 啓用 annotation事務-->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 採用JdbcTemplate配置 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="lobHandler" class="org.springframework.jdbc.support.lob.OracleLobHandler">
</bean>
<!-- 經過aop配置事務 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<aop:aspectj-autoproxy/>
<!-- 配置Spring Data JPA掃描目錄-->
<jpa:repositories base-package="cn.com.egova.easyshare.server.repository"/>
</beans>
複製代碼 |
而後在須要使用緩存的entity配置好緩存註解
session
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
複製代碼 |
package cn.com.egova.easyshare.common.entity;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
/**
* 系統菜單
* Created by gongxufan on 2016/3/13.
*/
@Entity
@Table(name = "TCSYSNAVMENU")
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
@Cacheable(true)
public class NavMenu {
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "tableKeyGenerator")
@TableGenerator(name = "tableKeyGenerator", table = "tcTableKeyGenerator",
pkColumnName = "pk_key", valueColumnName = "pk_value", pkColumnValue = "menuID",
initialValue = 1, allocationSize = 1)
private Integer menuID;
private Integer seniorMenuID;
private String menuName;
private String menuDescr;
private String menuIconClass;
private String hrefUrl;
private Integer displayOrder;
public Integer getMenuID() {
return menuID;
}
public void setMenuID(Integer menuID) {
this.menuID = menuID;
}
public Integer getSeniorMenuID() {
return seniorMenuID;
}
public void setSeniorMenuID(Integer seniorMenuID) {
this.seniorMenuID = seniorMenuID;
}
public String getMenuName() {
return menuName;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
public String getMenuDescr() {
return menuDescr;
}
public void setMenuDescr(String menuDescr) {
this.menuDescr = menuDescr;
}
public String getMenuIconClass() {
return menuIconClass;
}
public void setMenuIconClass(String menuIconClass) {
this.menuIconClass = menuIconClass;
}
public String getHrefUrl() {
return hrefUrl;
}
public void setHrefUrl(String hrefUrl) {
this.hrefUrl = hrefUrl;
}
public Integer getDisplayOrder() {
return displayOrder;
}
public void setDisplayOrder(Integer displayOrder) {
this.displayOrder = displayOrder;
}
}
複製代碼 |
固然還要配置好ehcache.xml
app
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
複製代碼 |
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!-- Sets the path to the directory where cache .data files are created.
If the path is a Java System Property it is replaced by
its value in the running VM.
The following properties are translated:
user.home - User's home directory user.dir - User's current working directory
java.io.tmpdir - Default temp file path -->
<!-- 緩存寫入文件目錄 -->
<diskStore path="user.home"/>
<!--Default Cache configuration. These will applied to caches programmatically created through
the CacheManager.
The following attributes are required for defaultCache:
maxInMemory - Sets the maximum number of objects that will be created in memory
eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the element
is never expired.
timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
if the element is not eternal. Idle time is now - last accessed time
timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
if the element is not eternal. TTL is now - creation time
overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache
has reached the maxInMemory limit.
-->
<!-- 數據過時策略 -->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<!--Predefined caches. Add your cache configuration settings here.
If you do not have a configuration for your cache a WARNING will be issued when the
CacheManager starts
The following attributes are required for defaultCache:
name - Sets the name of the cache. This is used to identify the cache. It must be unique.
maxInMemory - Sets the maximum number of objects that will be created in memory
eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the element
is never expired.
timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
if the element is not eternal. Idle time is now - last accessed time
timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
if the element is not eternal. TTL is now - creation time
overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache
has reached the maxInMemory limit.
-->
<!-- Sample cache named sampleCache1
This cache contains a maximum in memory of 10000 elements, and will expire
an element if it is idle for more than 5 minutes and lives for more than
10 minutes.
If there are more than 10000 elements it will overflow to the
disk cache, which in this configuration will go to wherever java.io.tmp is
defined on your system. On a standard Linux system this will be /tmp" --> <!-- Sample cache named sampleCache2 This cache contains 1000 elements. Elements will always be held in memory. They are not expired. --> <!-- <cache name="sampleCache2" maxElementsInMemory="1000" eternal="true" timeToIdleSeconds="0" timeToLiveSeconds="0" overflowToDisk="false" /> --> <!-- Place configuration for your caches following --> </ehcache> 複製代碼 |
運行測試代碼會發現兩次執行只有一次sql查詢
前兩種都是對主鍵查詢時候的緩存,對於普通的sql查詢咱們能夠在repository中配置hints
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
複製代碼 |
package cn.com.egova.easyshare.server.repository;
import cn.com.egova.easyshare.common.entity.Human;
import cn.com.egova.easyshare.common.entity.NavMenu;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import javax.persistence.QueryHint;
import java.util.List;
/**
* Created by gongxufan on 2016/3/13.
*/
public interface SysNavRepository extends PagingAndSortingRepository<NavMenu, Integer> {
@Query("select n from NavMenu n where exists (select 1 from SysNavRight s " +
"where n.menuID=s.sysNavMenuID and s.humanID=:humanId) order by n.displayOrder")
@QueryHints({ @QueryHint(name = "org.hibernate.cacheable", value ="true") })
List<NavMenu> findMenuList(@Param("humanId") Integer humanId);
@Query("select m from NavMenu m order by m.displayOrder")
List<NavMenu> findAllMenuList();
}
複製代碼 |
運行測試代碼
1
2
3
4
5
複製代碼 |
@Test
public void queryCacheTest() throws Exception{
List<NavMenuDto> navMenuDtos = humanService.getMenuList(37);
List<NavMenuDto> navMenuDtos2 = humanService.getMenuList(37);
}
複製代碼 |
控制檯輸出
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
複製代碼 |
Hibernate:
select
navmenu0_.menuID as menuID1_4_,
navmenu0_.displayOrder as displayOrder2_4_,
navmenu0_.hrefUrl as hrefUrl3_4_,
navmenu0_.menuDescr as menuDescr4_4_,
navmenu0_.menuIconClass as menuIconClass5_4_,
navmenu0_.menuName as menuName6_4_,
navmenu0_.seniorMenuID as seniorMenuID7_4_
from
TCSYSNAVMENU navmenu0_
where
exists (
select
1
from
TCHUMANSYSRIGHTS sysnavrigh1_
where
navmenu0_.menuID=sysnavrigh1_.sysNavMenuID
and sysnavrigh1_.humanID=?
)
order by
navmenu0_.displayOrder
2016-07-20 14:32:13 INFO main org.hibernate.engine.internal.StatisticalLoggingSessionEventListener - Session Metrics {
445013 nanoseconds spent acquiring 1 JDBC connections;
0 nanoseconds spent releasing 0 JDBC connections;
179244800 nanoseconds spent preparing 1 JDBC statements;
15228160 nanoseconds spent executing 1 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
20012371 nanoseconds spent performing 39 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
145920 nanoseconds spent performing 1 L2C misses;
0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}
2016-07-20 14:32:13 INFO main org.hibernate.engine.internal.StatisticalLoggingSessionEventListener - Session Metrics {
0 nanoseconds spent acquiring 0 JDBC connections;
0 nanoseconds spent releasing 0 JDBC connections;
0 nanoseconds spent preparing 0 JDBC statements;
0 nanoseconds spent executing 0 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
1620055 nanoseconds spent performing 39 L2C hits;
36694 nanoseconds spent performing 2 L2C misses;
0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}
複製代碼 |
這能夠看到第二次執行一樣的查詢會有L2C緩存命中
如何配置二級緩存和注意事項
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
複製代碼 |
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>${spring-data-jpa.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>${hibernate.version}</version>
</dependency>
複製代碼 |
必定要注意ehcache和hibernate的版本要一致,否則啓動會報錯。
1
2
3
4
5
6
7
複製代碼 |
<prop key="hibernate.generate_statistics">true</prop>
<!-- 配置二級緩存 -->
<prop key="hibernate.cache.use_second_level_cache">true</prop>
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
<!-- 開啓查詢緩存 -->
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.cache.provider_configuration">classpath:ehcache.xml</prop>
複製代碼 |
這裏開啓了二級緩存和查詢緩存,指定了ehcache緩存的配置文件statistics是個調試開關,能夠查詢緩存的命中狀況
1
2
3
4
5
6
7
複製代碼 |
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
複製代碼 |
timeToIdleSeconds
這個是緩存的最大空閒時間,也就是多久不訪問自動失效timeToLiveSeconds
這個是最大存活時間,若是在存活時間內空閒時間達到上限,緩存也會自動失效。
4)給entity配置緩存策略
1
2
複製代碼 |
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
@Cacheable(true)
複製代碼 |
cacheable主要對集合類的緩存提供支持,緩存策略這裏適合數據更新很少的狀況進行設置。
使用二級緩存和查詢緩存要注意使用場景,若是發現增長緩存機制系統反而出現吞吐降低頻發宕機。就要考慮是否是該換memcache等專門的緩存服務器了。