* @author lscl
* @version 1.0
* @intro:
*/
public interface EmpDao {
Emp findById(Integer id);
void deleteById(Integer id);
}
* EmpDao.xml:
<?xml version="1.0" encoding="UTF-8"?>
<delete id="deleteById">
delete from emp where id=#{id}
</delete>
<select id="findById" resultType="Emp">
select * from emp where id=#{id};
</select>
#### 1.1.2 MyBatis内置数据源
我们的数据源配置就是在 SqlMapConfig.xml 文件中,具体配置如下:
<?xml version="1.0" encoding="utf-8" ?>
<!--日志配置-->
<settings>
<setting name="logImpl" value="STDOUT\_LOGGING"/>
</settings>
<!-- default 默认使用那个运行环境 -->
<environments default="dev">
<!--配置dev运行环境-->
<environment id="dev">
<transactionManager type="JDBC"></transactionManager>
<!--POOLED指定采用mybatis内置的连接池支持-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis01?characterEncoding=UTF8"></property>
<property name="username" value="root"></property>
<property name="password" value="admin"></property>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.dfbz.dao"/>
</mappers>
![在这里插入图片描述](https://img-blog.csdnimg.cn/b131eb87547e46bb82f198a457d24c31.png#pic_center)
>
> Tips:MyBatis 在初始化时,解析此文件,根据`<dataSource>`的type属性来创建相应类型的的数据源
>
>
>
在MyBatis的源码包中可以看到MyBatis对数据源的分类:
![在这里插入图片描述](https://img-blog.csdnimg.cn/ae7649cb3a994d16a42b16ff761f4ac1.png#pic_center)
---
我们使用的`POOLED`、`UNPOOLED`、`JNDI`都是使用连接池的别名,我们可以通过查看`Configuration`类获取真正的类:
![在这里插入图片描述](https://img-blog.csdnimg.cn/d717fc2494c7429cbef8d2ba775b34e4.png#pic_center)
别名对应的类:
* POOLED:PooledDataSourceFactory—>PooledDataSource
* UNPOOLED:UnpooledDataSourceFactory—>UnpooledDataSource
* JNDI:JndiDataSourceFactory—>JndiDataSource
我们发现这三个类实现与`DataSourceFactory`接口:
![在这里插入图片描述](https://img-blog.csdnimg.cn/889cf39c2b0d4d7ea3b8d0bff50e1e6a.png#pic_center)
##### 1)UNPOOLED
采用这种方式,Mybatis内部创建`PooledDataSource`作为数据源,**如果采用UNPOOLED方式,Mybatis将不会使用数据源,每次查询都会创建一个新的连接;**
这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。
* **UNPOOLED数据源相关的配置属性如下:**
+ **`driver`:这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。**
+ **`url`:这是数据库的 JDBC URL 地址。**
+ **`username`:登录数据库的用户名。**
+ **`password`:登录数据库的密码。**
+ **`defaultTransactionIsolationLevel`:默认的连接事务隔离级别。**
+ **`defaultNetworkTimeout`:等待数据库操作完成的默认网络超时时间(单位:毫秒)。**
配置SqlMapConfig.xml:
<!--
事务隔离级别:
NONE(0): 不支持事务(MySQL等数据库并不支持该选项)
READ_UNCOMMITTED(1): 读未提交
READ_COMMITTED(2): 读已提交
REPEATABLE_READ(4): 可重复读
SERIALIZABLE(8): 串行化
–>
<!--SQL语句的执行超时时间-->
<property name="defaultNetworkTimeout" value="20000"></property>
</dataSource>
</environment>
* 测试类:
package com.dfbz.mybatis;
import com.dfbz.dao.EmpDao;
import com.dfbz.entity.Emp;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_UNPooled {
SqlSessionFactory factory;
@Before
public void before() throws Exception {
factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml"));
}
@Test
public void test1() throws Exception {
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
Emp emp = mapper.findById(13);
System.out.println(emp);
session.close();
}
@Test
public void test2() throws Exception {
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
mapper.deleteById(13);
session.rollback();
session.close();
}
}
UNPOOLED的特点在于,如果采用UNPOOLED方式,Mybatis将不会使用数据源,每次查询都会创建一个新的连接;
我们翻开`UnpooledDataSource`源码,发现其每次在获取连接时,都是调用原生的JDBC去创建一个新的连接,并没有对连接进行连接池的管理;
![在这里插入图片描述](https://img-blog.csdnimg.cn/fb176c423df9474e9ae37347309771a2.png#pic_center)
在使用UNPOOLED连接池策略时,每次获取一个session,都会调用`UnpooledDataSource`类的`doGetConnection`方法来获取一个新的连接,**而该方法每次调用都会使用DriverManager类来创建一个新的连接;**
![在这里插入图片描述](https://img-blog.csdnimg.cn/43cf154ede29471f8ace434a63c5bfdd.png#pic_center)
##### 2)POOLED
这种方式MyBatis会创建 PooledDataSource 实例来管理数据库连接(Connection)
这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:
* **`poolMaximumActiveConnections`:连接池中最大的活跃连接数量,默认值:10**
* **`poolMaximumIdleConnections`:连接池最大的空闲连接数,默认值:5**
* **`poolMaximumCheckoutTime`:获取连接的超时时间,超出该时间还未获取到连接MyBatis将会强制获取连接;默认值:20000 毫秒(即 20 秒)**
* **`poolTimeToWait`:如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。**
配置SqlMapConfig.xml:
<!--采用POOLED连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis01?characterEncoding=UTF8"></property>
<property name="username" value="root"></property>
<property name="password" value="admin"></property>
<!--连接池中最大的活跃连接数量-->
<property name="poolMaximumActiveConnections" value="5"></property>
<!--连接池最大的空闲连接数-->
<property name="poolMaximumIdleConnections" value="0"></property>
<!--获取连接的超时时间,超出该时间还未获取到连接MyBatis将会强制获取连接(获取poolMaximumActiveConnections个)-->
<property name="poolMaximumCheckoutTime" value="3000"></property>
<!--有多久没有获取到连接就打印日志-->
<property name="poolTimeToWait" value="1000"></property>
</dataSource>
</environment>
* 测试类:
package com.dfbz.mybatis;
import com.dfbz.dao.EmpDao;
import com.dfbz.entity.Emp;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.util.HashSet;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_Pooled {
SqlSessionFactory factory;
@Before
public void before() throws Exception {
factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml"));
}
@Test
public void test1() throws Exception {
HashSet<Object> sessionCount = new HashSet<>();
for (int i = 1; i <= 20; i++) {
// 获取新的session
SqlSession session = factory.openSession();
// 执行查询(使其成为活跃连接)
Object emp = session.selectOne("com.dfbz.dao.EmpDao.findById", 9);
// 使用HashSet去重特点,统计创建session的次数
sessionCount.add(session.toString());
}
System.out.println(sessionCount.size()); // 查看最终创建了多少个连接(20个)
}
@Test
public void test2() throws Exception {
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
Emp emp = mapper.findById(10);
System.out.println(emp);
session.close();
}
}
选择Pooled方式MyBatis底层则会创建`PooledDataSource`类来对连接进行管理,该类中提供`PoolState`类负责存储连接;
`PooledDataSource`类的成员变量如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/f754c92807c84793bded804b917ec665.png#pic_center)
`PoolState`:
![在这里插入图片描述](https://img-blog.csdnimg.cn/31b91595c40647969ac48ae8f6e63fce.png#pic_center)
采用`POOLED`方案管理连接时,每次获取一个连接都会调用`PooledDataSource`类的`popConnection`方法**从数据源中获取一个连接:**
![在这里插入图片描述](https://img-blog.csdnimg.cn/86bd54b704574a8a81499aef64c89a17.png#pic_center)
##### 3)JNDI
这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,MyBatis会从容器发布的JNDI服务上查找 DataSource 实例;
#### 1.1.3 自定义连接池
* 自定义MyDataSourceFactory:
package com.dfbz.datasource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.apache.ibatis.datasource.DataSourceFactory;
import javax.sql.DataSource;
import java.util.Properties;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class MyDataSourceFactory implements DataSourceFactory {
private DataSource dataSource;
private Properties properties;
@Override
public void setProperties(Properties properties) {
// MyBatis在初始化时会读取SqlMapConfig.xml中的properties配置,然后将读取到的配置传递到这个方法中
this.properties=properties;
try {
dataSource=DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public DataSource getDataSource() {
// Mybatis需要获取连接时首先从这个方法这里获取数据源(该方法只会调用一次)
return dataSource;
}
}
* 注册我们自己的连接池:
<!--注册我们自己的连接池-->
<dataSource type="com.dfbz.datasource.MyDataSourceFactory">
<property name="driver" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=UTF8"></property>
<property name="username" value="root"></property>
<property name="password" value="admin"></property>
<!--初始化连接数(具体数据源的配置)-->
<property name="initialSize" value="5"></property>
<!--最大连接数(具体数据源的配置)-->
<property name="maxActive" value="15"></property>
<!--最长等待时间(具体数据源的配置)-->
<property name="maxWait" value="3000"></property>
</dataSource>
</environment>
* 测试类:
package com.dfbz.demo;
import com.dfbz.entity.Emp;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.HashSet;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo04_自定义连接池 {
InputStream is;
SqlSessionFactoryBuilder sessionFactoryBuilder;
SqlSessionFactory factory;
@Before
public void before() throws Exception{
is = Resources.getResourceAsStream("SqlMapConfig.xml");
sessionFactoryBuilder = new SqlSessionFactoryBuilder();
factory = sessionFactoryBuilder.build(is);
}
@After
public void after() throws Exception{
is.close();
}
@Test
public void test1() throws Exception {
HashSet<Object> sessionCount = new HashSet<>();
for (int i = 1; i <= 20; i++) {
// 获取新的session
SqlSession session = factory.openSession();
// 执行查询(使其成为活跃连接)
Emp emp = session.selectOne("com.dfbz.dao.EmpDao.findById");
// 使用HashSet去重特点,统计创建session的次数
sessionCount.add(session.toString());
}
System.out.println(sessionCount.size()); // 查看最终创建了多少个连接(20个)
}
}
### 1.2 MyBatis事务管理
#### 1.2.1 JDBC
在 MyBatis 中有两种类型的事务管理器`JDBC`和`MANAGED`
* `JDBC`:使用原生的JDBC提交和回滚;它依赖从数据源获得的连接来管理事务作用域。
在SqlMapConfig.xml中选择:
>
> Tips:通过Configuration配置类可以查看JDBC别名的全称:`JdbcTransactionFactory`
>
>
>
查看JDBCTransactionManager部分源码:
![在这里插入图片描述](https://img-blog.csdnimg.cn/34cc51b0ada549dbb341a1a9409bb636.png#pic_center)
* 测试类:
package com.dfbz.demo;
import com.dfbz.dao.EmpDao;
import com.dfbz.entity.Emp;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_JDBC {
InputStream is;
SqlSessionFactoryBuilder sessionFactoryBuilder;
SqlSessionFactory factory;
SqlSession session ;
@Before
public void before() throws Exception{
is = Resources.getResourceAsStream("SqlMapConfig.xml");
sessionFactoryBuilder = new SqlSessionFactoryBuilder();
factory = sessionFactoryBuilder.build(is);
}
@After
public void after() throws Exception{
session.close();
is.close();
}
@Test
public void test1() throws Exception{
// 修改为自动提交
session = factory.openSession(true);
// 修改隔离级别为RU
// session = factory.openSession(TransactionIsolationLevel.READ_UNCOMMITTED);
EmpDao mapper = session.getMapper(EmpDao.class);
// mapper.deleteById(7);
Emp emp = mapper.findById(1);
System.out.println(emp);
session.commit();
session.close();
}
}
#### 1.2.2 MANAGED
* `MANAGED`:这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。
>
> 通过Configuration配置类可以查看MANAGED的全称:`ManagedTransactionFactory`
>
>
>
我们翻开`ManagedTransactionFactory`类的源码查看:
![在这里插入图片描述](https://img-blog.csdnimg.cn/816e478c7a73406c83bb3ae91d3b98c2.png#pic_center)
发现`ManagedTransactionFactory`在提交和回滚上是没有做任何操作的;因此如果事务管理器设置为了`MANAGED`,那么事务将不支持回滚功能;
测试:
package com.dfbz.demo;
import com.dfbz.dao.EmpDao;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_Managed {
InputStream is;
SqlSessionFactoryBuilder sessionFactoryBuilder;
SqlSessionFactory factory;
SqlSession session ;
@Before
public void before() throws Exception{
is = Resources.getResourceAsStream("SqlMapConfig.xml");
sessionFactoryBuilder = new SqlSessionFactoryBuilder();
factory = sessionFactoryBuilder.build(is);
}
@After
public void after() throws Exception{
session.close();
is.close();
}
@Test
public void test1() throws Exception{
/\*
autoCommit设置的值对于Managed事务管理器无效
因为Managed提交或者回滚事务都不会由MyBatis来处理,交给默认的连接来处理(默认自动提交)
*/
session = factory.openSession(false);
EmpDao mapper = session.getMapper(EmpDao.class);
mapper.deleteById(1);
session.close();
}
}
观察数据库,发现数据直接提交,而不是回滚
#### 1.2.3 自定义事务管理器
我们自定义事务管理器需要实现`TransactionFactory`接口:
![在这里插入图片描述](https://img-blog.csdnimg.cn/18133b437cfc49f3a3b195b1f05ff871.png#pic_center)
##### 1)定义事务处理器(Transaction):
package com.dfbz.transaction;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class MyTransaction implements Transaction {
protected Connection connection;
protected DataSource dataSource;
protected TransactionIsolationLevel level;
protected boolean autoCommit;
public MyTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
dataSource = ds;
level = desiredLevel;
autoCommit = desiredAutoCommit;
}
public MyTransaction(Connection connection) {
this.connection = connection;
}
@Override
public Connection getConnection() throws SQLException {
if(connection==null){
// 从连接池中获取一个连接
this.connection=dataSource.getConnection();
// 设置事务的提交方式
this.connection.setAutoCommit(this.autoCommit);
}
if (this.level != null) {
// 设置事本次连接的事务隔离级别
this.connection.setTransactionIsolation(this.level.getLevel());
}
return this.connection;
}
// 只有自动提交手动设置为false了,在session.commit()时才会调用事务管理器的提交方法来提交事务
@Override