Java for Web学习笔记(一零六):Spring框架中使用JPA(6)Isolation和C3P0(下)

使用hibernate-c3p0的isolation设置

我们前面学习的方式,都是先设置datasource,然后在JPA实现中关联该数据源。hibernate提供了hibernate-c3p0,在提供entityManagerFactoryBean的时候进行数据源的设置,实际上采用xml文件配置[1],很多都是这种方式,只不过我们采用代码方式实现。

pom.xml的设置,将将<artifactId>hibernate-core</artifactId> 修改为<artifactId>hibernate-c3p0</artifactId>,将自动引入相应的hibernate-core和c3p0的jar包。

/* 修订:删除DataSource的bean,由hibernate实现的entityManagerFactoryBean来自动关联
@Bean
public DataSource springJpaDataSource() throws Exception{
    ......
}*/
	
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() throws Exception {
	MyConfig config = this.myConfig(); //这个模拟获取的配置,可以从中得到数据源的配置
	Map<String, Object> properties = new Hashtable<>();
	properties.put("javax.persistence.schema-generation.database.action", "none");
	properties.put("hibernate.connection.url", config.getJdbcUrl() + "?useSSL=true&useUnicode=true&characterEncoding=utf-8");
	properties.put("hibernate.connection.username", config.getJdbcUser());
	properties.put("hibernate.connection.password", config.getJdbcPassword());
	properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
	properties.put("hibernate.connection.driver_class", "com.mysql.jdbc.Driver");
	properties.put("connection.provider_class", "org.hibernate.c3p0.internal.C3P0ConnectionProvider");		
	properties.put("hibernate.c3p0.min_size", "2");
	properties.put("hibernate.c3p0.max_size", "200");
	properties.put("hibernate.c3p0.timeout", "1800");
	properties.put("hibernate.c3p0.acquire_increment", "2");	
	//这里设置isolation
	properties.put("hibernate.connection.isolation", String.valueOf(Connection.TRANSACTION_REPEATABLE_READ));
		
	HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
	LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
	factory.setJpaVendorAdapter(adapter);
	factory.setPackagesToScan("com.wei.testproject.entities");
	factory.setSharedCacheMode(SharedCacheMode.ENABLE_SELECTIVE);
	factory.setValidationMode(ValidationMode.NONE);
	factory.setJpaPropertyMap(properties);
	return factory;
注意,只要我们不使用tomcat的数据源方式,对于mysql都需要进行driver的deregister。否则卸载时出现:

一月 12, 2018 10:13:53 上午 org.apache.catalina.loader.WebappClassLoaderBase clearReferencesJdbc 
警告: The web application [abc] registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister it when the  web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.
一月 12, 2018 10:13:53 上午 org.apache.catalina.loader.WebappClassLoaderBase clearReferencesThreads
警告: The web application [abc] appears to have started a thread named [Abandoned connection cleanup thread] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
 java.lang.Object.wait(Native Method)
 java.lang.ref.ReferenceQueue.remove(Unknown Source)
 com.mysql.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:64)
 java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
 java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
j ava.lang.Thread.run(Unknown Source)
一月 12, 2018 10:13:57 上午 org.apache.catalina.loader.WebappClassLoaderBase checkStateForResourceLoading
信息: Illegal access: this web application instance has been stopped already. Could not load []. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access.
java.lang.IllegalStateException: Illegal access: this web application instance has been stopped already. Could not load []. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access.
	at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading(WebappClassLoaderBase.java:1352)
	at org.apache.catalina.loader.WebappClassLoaderBase.getResource(WebappClassLoaderBase.java:1028)
	at com.mysql.jdbc.AbandonedConnectionCleanupThread.checkContextClassLoaders(AbandonedConnectionCleanupThread.java:90)
	at com.mysql.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:63)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.lang.Thread.run(Unknown Source)
在C3P0Utils中,我们已经写过destroy,我们只需抽取其注销driver的部分接口,如下。同样,我们应在war结束时调用。

public static void closeSafely(){
	com.mysql.jdbc.AbandonedConnectionCleanupThread.checkedShutdown();
		
	Enumeration<java.sql.Driver> drivers = DriverManager.getDrivers();
	while(drivers.hasMoreElements()) {
		try {				
			Driver driver = drivers.nextElement();
			DriverManager.deregisterDriver(driver);
		} catch (SQLException e) {
			logger.error("Close mysql safely error : {}", e.toString());
		}
	}
}

代码中修订isolation等级

无论采用上面哪种方式设置,我们都可以通@Transactional重新指定isolation等级。

@Override
@Transactional(isolation=Isolation.READ_COMMITTED)
void test1(){}

isolation不能解决的DataIntegrityViolationException的处理

测试小例子

我们进行了一下的一个测试,我们通过两个异步调用,几乎同时执行了下面的两个事务

//ActionService:
@Transaction
public viod save(String event){
    //saveHistory()是如果不存在则insert,如果存在则update信息
    this.historyRepository.saveHistory(event);
}
这是报告下面的错误:

16:23:50.293 [task-7] [ERROR] (Spring) SimpleAsyncUncaughtExceptionHandler - Unexpected error occurred invoking async method 'public void com.wei.testproject.site.ActionService.actionAsync(com.wei.testproject.entities.ActionData)'.
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [PRIMARY]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
       at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:278) ~[spring-orm-4.3.11.RELEASE.jar:4.3.11.RELEASE]
       at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244) ~[spring-orm-4.3.11.RELEASE.jar:4.3.11.RELEASE]
       at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521) ~[spring-orm-4.3.11.RELEASE.jar:4.3.11.RELEASE]
       at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761) ~[spring-tx-4.3.11.RELEASE.jar:4.3.11.RELEASE]
       at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730) ~[spring-tx-4.3.11.RELEASE.jar:4.3.11.RELEASE]
       ......

分析

我们重点看看api的对Connection.TRANSACTION_REPEATABLE_READ的javadoc说明:
A constant indicating that dirty reads and non-repeatable reads are prevented; phantom reads can occur. This level prohibits a transaction from reading a row with uncommitted changes in it, and it also prohibits the situation where one transaction reads a row, a second transaction alters the row, and the first transaction rereads the row, getting different values the second time (a "non-repeatable read").

这能够对update的情况进行很好的保护,但是小测试中在两个事务中都在尝试insert,不在其保护范围内。虽然在实际运行中,几乎不可能出现这种情况,但是作为代码的完整性,我们还是应该进行相应的修订。

代码修订

由于transaction中检查到异常会进行回滚,因此,我们不能在transaction的内部代码中进行修订,需要在外部。为此我们新建一个AdvanceActionService类来替代ActionService进行相关的处理。

//AdvanceActionService:
@Inject IActionService actionService;

// 对于存在类似save的情况,均采用:
public viod save(String event){
    try{
        this.actionService.save(event);
    }catch(DataIntegrityViolationException e){
        log.warn("Try to fix DataIntegrityViolationException, retry save again.");
        this.actionService.save(event);
    }
}
在实际应用中,我们很少采用这种修订方式,同时insert本来就是很少的概率,在具体的应用场景下,可能根本就不会发生,如果是用户的UI操作,有先后顺序,如果是消息队列也有先后顺序。亦或者是属于极个别的极偶然的现象,第二个insert就抛出异常,并回滚,那就按异常情况处理好了。但是如果在某些特定的场景,有一定概率的出现,就需要进行修订。

相关链接:我的Professional Java for Web Applications相关文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值