事务管理:
MyBatis事务的设计重点是Transaction接口;
MyBatis事务管理分为两种:
- JdbcTransaction:即利用java.sql.Connection对象完成对事务的提交、回滚、关闭等;
- ManagedTransaction:这种机制MyBatis自身不会实现事务管理,而是让程序的容器来实现对事务的管理;
事务的配置:
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="org.gjt.mm.mysql.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1/test?useUnicode=true&characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
事务的创建:
事务的创建是交给TransactionFactory来完成,当<transactionManager type="JDBC" />时,MyBatis初始化解析<environment>节点时,会根据其创建JdbcTransactionFactory;
JdbcTransaction实现类:Transaction的实现类,通过使用jdbc提供的方式来管理事务,通过Connection提供的事务管理方法来进行事务管理,源码如下:
public class JdbcTransaction implements Transaction {
private static final Log log = LogFactory.getLog(JdbcTransaction.class);
/* 连接**/
protected Connection connection;
/* 数据源**/
protected DataSource dataSource;
/* 事务等级**/
protected TransactionIsolationLevel level;
/* 事务提交**/
protected boolean autoCommmit;
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
dataSource = ds;
level = desiredLevel;
autoCommmit = desiredAutoCommit;
}
public JdbcTransaction(Connection connection) {
this.connection = connection;
}
@Override
public Connection getConnection() throws SQLException {
if (connection == null) {
openConnection();
}
//返回连接
return connection;
}
@Override
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Committing JDBC Connection [" + connection + "]");
}
//连接提交
connection.commit();
}
}
@Override
public void rollback() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Rolling back JDBC Connection [" + connection + "]");
}
//连接回滚
connection.rollback();
}
}
@Override
public void close() throws SQLException {
if (connection != null) {
resetAutoCommit();
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + connection + "]");
}
//关闭连接
connection.close();
}
}
protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
try {
//事务提交状态不一致时修改
if (connection.getAutoCommit() != desiredAutoCommit) {
if (log.isDebugEnabled()) {
log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
}
connection.setAutoCommit(desiredAutoCommit);
}
} catch (SQLException e) {
// Only a very poorly implemented driver would fail here,
// and there's not much we can do about that.
throw new TransactionException("Error configuring AutoCommit. "
+ "Your driver may not support getAutoCommit() or setAutoCommit(). "
+ "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e);
}
}
protected void resetAutoCommit() {
try {
if (!connection.getAutoCommit()) {
// MyBatis does not call commit/rollback on a connection if just selects were performed. select操作没有commit和rollback事务
// Some databases start transactions with select statements 一些数据库在select操作是会开启事务
// and they mandate a commit/rollback before closing the connection.
// A workaround is setting the autocommit to true before closing the connection.
// Sybase throws an exception here.
if (log.isDebugEnabled()) {
log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
}
connection.setAutoCommit(true);
}
} catch (SQLException e) {
if (log.isDebugEnabled()) {
log.debug("Error resetting autocommit to true "
+ "before closing the connection. Cause: " + e);
}
}
}
//打开连接
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
//从数据源中获得连接
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommmit);
}
}
ManagedTransaction实现类:通过容器来进行事务管理,所有它对事务提交和回滚并不会做任何操作,源码如下:
public class ManagedTransaction implements Transaction {
private static final Log log = LogFactory.getLog(ManagedTransaction.class);
private DataSource dataSource;
private TransactionIsolationLevel level;
private Connection connection;
private boolean closeConnection;
public ManagedTransaction(Connection connection, boolean closeConnection) {
this.connection = connection;
this.closeConnection = closeConnection;
}
//数据源,事务等级及是否关闭事务
public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) {
this.dataSource = ds;
this.level = level;
this.closeConnection = closeConnection;
}
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
//提交操作无效
@Override
public void commit() throws SQLException {
// Does nothing
}
//回滚操作无效
@Override
public void rollback() throws SQLException {
// Does nothing
}
@Override
public void close() throws SQLException {
if (this.closeConnection && this.connection != null) {
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + this.connection + "]");
}
//关闭连接
this.connection.close();
}
}
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
this.connection = this.dataSource.getConnection();
if (this.level != null) {
this.connection.setTransactionIsolation(this.level.getLevel());
}
}
}
缓存机制:
MyBatis的缓存分为两级:一级缓存、二级缓存
- 一级缓存是SqlSession级别的缓存,缓存的数据只在SqlSession内有效;
- 二级缓存是mapper级别的缓存,同一个namespace公用这一个缓存,所以对SqlSession是共享的;
- 缓存机制减轻数据库压力,提高数据库性能;
一级缓存:
MyBatis的一级缓存是SqlSession级别的缓存。在操作数据库时需要构造SqlSession对象,在对象中有一个HashMap用于存储缓存数据,不同的SqlSession之间的缓存数据区域互不影响;
- MyBatis的缓存机制是基于id进行的缓存,即使用对象的id作为key,而对象作为value保存;
一级缓存的作用域是SqlSession范围的,在同一个SqlSession中执行两次相同的sql语句时,第一次执行完毕会将查询到的数据写到缓存(内存),第二次查询时会从缓存中查询数据,不再去底层数据库查询,提高效率。
- 如果SqlSession执行了delete、insert、update操作并提交到数据库,MyBatis会清空一级缓存,保证了缓存中存储最新的信息,避免脏读,当一个SqlSession结束后该SqlSession中的一级缓存也就不存在;
测试;
@Test
public void query(){
SqlSession sqlSession=sqlSessionFactory.openSession();
try{
User user=sqlSession.selectOne("selectUserById",1);
System.out.println(user.getUsername());
System.out.println("------------------");
User user1=sqlSession.selectOne("selectUserById",1);
System.out.println(user1.getUsername());
}catch (Exception e){
e.printStackTrace();
}finally {
sqlSession.close();
}
}
二级缓存:
- 二级缓存是mapper级别的缓存,使用二级缓存时,多个SqlSession使用同一个Mapper的sql语句去操作数据库,得到的数据会存在二级缓存区域,它同样是使用HashMap进行数据存储,相比一级缓存,二级缓存的范围更大,多个SqlSession公用二级缓存,二级缓存是跨SqlSession;
- 二级缓存是多个SqlSession共享,其作用域是mapper的同一个namespace.不同的SqlSession两次执行相同的namespace下的sql语句,且向sql中传递的参数相同,即最终执行相同的sql语句,则第一次执行完毕会将查询到的数据写到缓存(内存),第二次查询时会从缓存中查询数据,不再去底层数据库查询,提高效率;
- MyBatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存;
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
<!-- 当前mapper下-->
<cache eviction="LRU" flushInterval="6000" size="512" readOnly="true"></cache>
@CacheNamespace(size = 512,flushInterval = 6000)
eviction:收回策略,默认LRU,
- LRU:最近最少使用策略,
- FIFO:先进先出策略,
- SOFT:软引用策略,移除基于垃圾回收器状态和软引用规则的对象,
- WEAK:弱引用策略,更积极的移除基于垃圾回收器状态和弱引用规则的对象
使用二级缓存时,与查询结果映射的Java对象必须实现接口的序列化和反序列化,因为二级缓存数据存储介质多种多样,不一定在内存,有可能是硬盘或者远程服务器;
测试:
@Test
public void query(){
SqlSession sqlSession=sqlSessionFactory.openSession();
try{
User user=sqlSession.selectOne("selectUserById",1);
System.out.println(user.getUsername());
sqlSession.close();
System.out.println("------------------");
sqlSession=sqlSessionFactory.openSession();
User user1=sqlSession.selectOne("selectUserById",1);
System.out.println(user1.getUsername());
}catch (Exception e){
e.printStackTrace();
}finally {
sqlSession.close();
}
}
当关闭一级缓存时,会从二级缓存中查找,即只执行一次sql语句;