1. 程式人生 > >springBoot中啟用事務管理

springBoot中啟用事務管理

在資料庫的操作中事務的重要性相信大家都知道,能夠保證資料的原子性,一致性,完整性等... 使用步驟相對蠻簡單,下面就直接簡要介紹 springBoot 中應用事務功能;

一,定義/配置資料來源Bean並加入spring容器

如果使用預設的JDBC資料庫連線池則只需要配置資料庫的連線資訊(當然相應的資料庫依賴jar要引入pom.xml)即可;如果使用其它資料庫連線池,如Druid,則需要定義或配置相應資料來源,為後面方便使用,故@Bean("dataSource")指定名稱,同時@Primary指定以此資料來源為優先,如下程式碼:

package com.qyh.pro01.common;

import java.sql.SQLException;
import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.alibaba.druid.pool.DruidDataSource;

/**
 * 配置啟用Druid連線池,定義
 * @author shenzhenNBA
 * @since 2018.06.28
 */

@Configuration    //該註解類似於spring配置檔案  
public class MyDataSourceConfig extends WebMvcAutoConfiguration {
	
    public MyDataSourceConfig() { 
	super();
    }

    private Logger logger = LoggerFactory.getLogger(MyDataSourceConfig.class);  
	
    @Autowired  
    private Environment env; 
		
    @Value("${spring.datasource.type}") //連線資訊配置見application.properties
    private String dbType;    
	
    @Value("${spring.datasource.url}")    
    private String dbUrl;    
        
    @Value("${spring.datasource.username}")    
    private String username;    
        
    @Value("${spring.datasource.password}")    
    private String password;    
        
    @Value("${spring.datasource.driverClassName}")    
    private String driverClassName;    
        
    @Value("${spring.datasource.initialSize}")    
    private int initialSize;    
        
    @Value("${spring.datasource.minIdle}")    
    private int minIdle;    
        
    @Value("${spring.datasource.maxActive}")    
    private int maxActive;    
        
    @Value("${spring.datasource.maxWait}")    
    private int maxWait;    
        
    @Value("${spring.datasource.timeBetweenEvictionRunsMillis}")    
    private int timeBetweenEvictionRunsMillis;    
        
    @Value("${spring.datasource.minEvictableIdleTimeMillis}")    
    private int minEvictableIdleTimeMillis;    
        
    @Value("${spring.datasource.validationQuery}")    
    private String validationQuery;    
        
    @Value("${spring.datasource.testWhileIdle}")    
    private boolean testWhileIdle;    
        
    @Value("${spring.datasource.testOnBorrow}")    
    private boolean testOnBorrow;    
        
    @Value("${spring.datasource.testOnReturn}")    
    private boolean testOnReturn;    
        
    @Value("${spring.datasource.poolPreparedStatements}")    
    private boolean poolPreparedStatements;    
        
    @Value("${spring.datasource.maxPoolPreparedStatementPerConnectionSize}")    
    private int maxPoolPreparedStatementPerConnectionSize;    
        
    @Value("${spring.datasource.filters}")    
    private String filters;    
        
    @Value("{spring.datasource.connectionProperties}")    
    private String connectionProperties;  
	
    /**  
    * 建立資料來源,並載入進spring容器,跟在spring的配置檔案中的載入bean一樣;	
    */  
    @Bean("dataSource") //產生bean例項載入進srping容器中,指定Bean名稱
    @Primary  //當有多個實現時以此為優先為準
    public DataSource getDataSource() throws Exception{  
    	DruidDataSource datasource = new DruidDataSource(); 
    	
    	//datasource.setDbType(dbType);   //有些版本不支援該屬性
    	datasource.setUrl(dbUrl);  //連線資訊配置見application.properties
    	datasource.setDriverClassName(driverClassName);
        datasource.setUsername(username);
        datasource.setPassword(password);
        
        //configuration    
        datasource.setInitialSize(initialSize);    
        datasource.setMinIdle(minIdle);    
        datasource.setMaxActive(maxActive);    
        datasource.setMaxWait(maxWait);    
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);    
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);    
        datasource.setValidationQuery(validationQuery);    
        datasource.setTestWhileIdle(testWhileIdle);    
        datasource.setTestOnBorrow(testOnBorrow);    
        datasource.setTestOnReturn(testOnReturn);    
        datasource.setPoolPreparedStatements(poolPreparedStatements);    
        datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);    
        try {    
            datasource.setFilters(filters);    
        } catch (SQLException e) {    
            logger.error("druid configuration initialization filter", e);    
        }
        datasource.setConnectionProperties(connectionProperties);    
    	return datasource;    	
    } 
    
    
    /**  
     * 根據資料來源建立SqlSessionFactory,並載入進spring容器,
     * 同時配置MyBatis(庫表到Java實體的對映關係)用到的實體,實體別名和XML等
     */
    @Bean 	
    public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception{  
        SqlSessionFactoryBean sqlSFB = new SqlSessionFactoryBean();  
        sqlSFB.setDataSource(ds);
        //指定自定義的資料來源,這個必須用
        
        sqlSFB.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage")); 
        //指定對應的實體包,多個包之間逗號隔開,配置資訊見application.properties
        
        Resource configLocationResource = new PathMatchingResourcePatternResolver().getResource(
        		env.getProperty("mybatis.configLocations"));
        sqlSFB.setConfigLocation(configLocationResource);
        //指定mybatis的本地配置檔案資源,目的是定義實體等別名,可以不用,如果不用對應配置檔案應註釋掉
        
        Resource[] mapperLocations = new PathMatchingResourcePatternResolver().getResources(
        		env.getProperty("mybatis.mapperLocations"));
        sqlSFB.setMapperLocations(mapperLocations);
        //指定mybatis的庫表到實體的對映xml檔案的mapper資源  
        
        return sqlSFB.getObject();  
    }
    
}

確保在啟動類中掃描到該類所在的包;

二,定義/配置事務管理器Bean並加入spring容器

要啟用事務當然少不了定義事務管理器,需要在類前面使用註解 @EnableTransactionManagement 開啟事務並告知spring,同時作為配置類加註解 @Configuration 注入IOC容器,定義事務管理器 DataSourceTransactionManager 型別的bean,其引數中需要指定名稱的資料來源,如前一步所見,引數前可指定 @Qualifier("資料來源名稱"),例如下面程式碼:

package com.qyh.pro01.common;

import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * 配置事務管理器
 * @author shenzhenNBA
 * @since 2018.06.28
 */

//開啟事務管理,確保在啟動類中@component掃描到該類
@EnableTransactionManagement 
@Configuration
public class TransactionManagementConfig {
	
	//注意: @Qualifier 按名稱在IOC容器中找指定名稱的bean,
	@Bean //或者 @Bean("myTransactionManager")
	public PlatformTransactionManager platformTransactionManager(
			@Qualifier("dataSource") DataSource myDataSource) {
		return new DataSourceTransactionManager(myDataSource);
	}
	
}
注意的是,事務管理器可以有多個,當定義多個事務管理器時應指定名稱,如:@Bean("事務管理器名稱"),以便在service中使用時明確指定哪個事務管理器;同時確保在啟動類中掃描到該類所在的包;

三,在service層中使用 @Transactional 註解應用事務

事務的應用相對比較簡單,加個 @Transactional 註解即可完成事務應用,一般使用在資料庫操作的service層的類中使用,使用的位置分兩種一種是在類前,這個時候類中所有的方法預設都具有這種型別的事務;另一種應用在方法名稱前面,這時的事務會覆蓋類前面使用的事務,@Transactional註解只能應用到 public 方法才有效 (外部經過spring容器呼叫service的方法事務才生效,service類內部方法間相互呼叫事務不生效),@Transactional的使用格式如下

,其中的值可根據需要修改:

@Transactional(
	value="可自定義的事務管理器", //等同於 transactionManager="", 
	propagation=Propagation.REQUIRED, //根據需要修改
	timeout = -1,	//時間大於零時,超過時間未完成則事務回滾
	isolation = Isolation.DEFAULT,    //隔離級別
	readOnly = false,		//是否只讀 true or false
	rollbackFor = Exception.class,	//或者它異常類
	noRollbackFor = OutOfMemoryError.class	//或者它異常類
)

事務應用例子,如下:

package com.qyh.pro01.service.impl;

import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;

import com.qyh.pro01.dao.MemberService;
import com.qyh.pro01.model.Member;
import com.qyh.pro01.param.MemberParam;
import com.qyh.pro01.service.BizMemberService;

/**
 * 事務在service層中的使用
 * @author shenzhenNBA
 * @since 2018.06.28
 */

@Transactional(readOnly = true) //類中所有方法預設的事務
@Service("bizMemberService")
public class BizMemberServiceImpl implements BizMemberService<Member, Long> {
	
	private static Log LOG = LogFactory.getLog(BizMemberServiceImpl.class);
	
	private static final long serialVersionUID = 20180626010130L;
	
	@Autowired
	private MemberService<Member, Long> memberDaoService;

	@Override
	public Member getByUserRecId(Long recId) {
		return memberDaoService.getByUserRecId(recId);
	}
	
	@Override
	public Member getByRecId(Long recId) {
		return memberDaoService.getMemberByRecId(recId);
	}
	
	/*
	//完整格式
	@Transactional(transactionManager="", //等同於 value="可自定義的事務管理器"
			propagation=Propagation.REQUIRED, //根據需要修改
			timeout = -1,  //時間大於零時,超過時間未完成則事務回滾
			isolation = Isolation.DEFAULT,
			readOnly = false,	//true or false
			rollbackFor = Exception.class, 	//或者它異常類
			noRollbackFor = OutOfMemoryError.class 	 //獲取它異常類
	)
	*/
	@Transactional(propagation=Propagation.REQUIRED,rollbackFor = Exception.class)
	@Override
	public int insert(Member t) throws Exception {
		return memberDaoService.insert(t);
	}

	@Transactional(propagation=Propagation.REQUIRED,rollbackFor = Exception.class)
	@Override
	public int save(Member t) throws Exception {
		// TODO Auto-generated method stub
		return 0;
	}

	@Transactional(propagation=Propagation.REQUIRED,rollbackFor = Exception.class)
	@Override
	public int update(Member t) throws Exception {
		return memberDaoService.update(t);
	}

	@Transactional(propagation=Propagation.REQUIRED,rollbackFor = Exception.class)
	@Override
	public int delete(Member t) throws Exception {
		return memberDaoService.delete(t);
	}

	@Override
	public Member getMemberByRecId(Long recId) {
		return memberDaoService.getMemberByRecId(recId);
	}

	@Override
	public Member getMemberByMemberId(String memberId) {
		return memberDaoService.getMemberByMemberId(memberId);
	}

	@Override
	public List<Member> queryAllMember() {
		return memberDaoService.queryAllMember();
	}

	@Override
	public List<Member> queryByMember(MemberParam memberParam) {
		return memberDaoService.queryByMember(memberParam);
	}

	@Transactional(propagation=Propagation.REQUIRED,rollbackFor = Exception.class)
	@Override
	public int updateUserMoney(MemberParam memberParam) {
		return memberDaoService.updateUserMoney(memberParam);
	}

	@Transactional(propagation=Propagation.REQUIRED,rollbackFor = Exception.class)
	@Override
	public int deleteByMemberId(String memberId) {
		return memberDaoService.deleteByMemberId(memberId);
	}	
}

同時確保在啟動類中掃描到該類所在的包;

四,啟動類中掃描到定義資料來源類,事務管理器類,service層的類所在的包

例子中可見很多地方使用到註解,所有有個共同特點,就是要使相應註解生效,則在啟動類中必須確保@ComponentScan掃描到各個相應的工作類所在的包;

後記,相對來說得益於springboot中各種強大的註解,使得在springboot中使用事務相對來說蠻簡單的,可能難免有錯漏之處,歡迎拍磚評論...