一、MyBatis 的连接池技术分类
-
连接池就是一个用于存储连接的容器。这个容器其实就是一个集合对象,该集合必须是线程安全的,不能两个线程同时拿到同一个连接。该集合还必须有队列的特性:先进先出(归还的连接放在连接池队列的末尾)。
-
我们在实际开发中都会使用数据库连接池,因为它可以减少我们获取连接所消耗的时间。
-
我们在JDBC部分也学习过类似的连接池技术,而在 MyBatis 中也有连接池技术,但是它采用的是自己的连接池技术。在 MyBatis 的核心配置文件中,通过
<dataSource type="具体连接池技术">
来实现 MyBatis 中连接池的配置。 -
MyBatis核心配置文件中的dataSource标签,type属性就是表示采用何种连接池方式。
-
Mybatis 将它自己的数据源(连接池技术)分为三类(type属性的取值):
- POOLED:使用传统的 javax.sql.DataSource 规范中的连接池的数据源
- UNPOOLED:采用传统的获取连接的方式,虽然也实现 javax.sql.DataSource 接口,但不使用连接池的数据源
- JNDI:采用服务器提供的JNDI技术实现来获取DataSource对象,不同的服务器所能拿到DataSource是不一样的。
注意:如果不是web或者maven的war工程,是不能使用JNDI的。
我们如果使用的是Tomcat服务器,那么JNDI采用的连接池就是DBCP连接池。
在这三种数据源中,我们一般采用的是 POOLED 数据源。
(一)POOLED
MyBatis核心配置文件:
<!-- 配置数据源(连接池)信息 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
- MyBatis 在初始化时,根据
<dataSource>
的 type 属性来创建相应类型的的数据源 DataSource,即:
type="POOLED"
:MyBatis 会创建 PooledDataSource 实例
type="UNPOOLED"
: MyBatis 会创建 UnpooledDataSource 实例
type="JNDI"
:MyBatis 会从 JNDI 服务上查找 DataSource 实例,然后返回使用 - PooledDataSource和UnpooledDataSource都实现了 javax.sql.DataSource 接口。并且PooledDataSource持有一个UnpooledDataSource的引用,当PooledDataSource需要创建 java.sql.Connection 实例对象时,还是通过UnpooledDataSource来创建,PooledDataSource只是提供一种缓存连接池机制。
MyBatis中的DataSource的存取和连接的获取过程:
- MyBatis 是通过工厂模式来创建数据源 DataSource 对象的,MyBatis 定义了抽象的工厂接口:org.apache.ibatis.datasource.DataSourceFactory,通过其
getDataSource()
方法返回数据源DataSource。 - MyBatis 创建了 DataSource 实例后,会将其放到 Configuration 对象内的 Environment 对象中, 供以后使用。
- 当我们需要创建 SqlSession 对象并需要执行 SQL 语句时,这时候 MyBatis 才会去调用 dataSource 对象来创建java.sql.Connection对象。也就是说,java.sql.Connection对象的创建一直延迟到执行SQL语句的时候。数据库连接是我们最为宝贵的资源,只有在要用到的时候,才去获取并打开连接,当我们用完了就再立即将数据库连接归还到连接池中。
(二)UNPOOLED
MyBatis核心配置文件:
<!-- 配置数据源(连接池)信息 -->
<dataSource type="UNPOOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
(三)JNDI
JNDI:Java Naming and Directory Interface,Java命名和目录接口。是SUN公司推出的一套规范,属于JavaEE技术之一。目的是模仿windows系统中的注册表在服务器中注册数据源。
创建Maven的war工程并导入依赖:
配置自己安装的Maven版本(也可以选择默认的版本):
导入依赖:
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
在src/main下创建java、resource目录,并标记为对应的目录类型:
User实体类:
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private Integer userId;
private String userName;
private String userAddress;
private String userSex;
private Date userBirthday;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
public String getUserSex() {
return userSex;
}
public void setUserSex(String userSex) {
this.userSex = userSex;
}
public Date getUserBirthday() {
return userBirthday;
}
public void setUserBirthday(Date userBirthday) {
this.userBirthday = userBirthday;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userAddress='" + userAddress + '\'' +
", userSex='" + userSex + '\'' +
", userBirthday=" + userBirthday +
'}';
}
}
IUserDao:
import com.fox.pojo.User;
import java.util.List;
public interface IUserDao {
//查询所有用户
List<User> findAll();
}
IUserDao.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fox.dao.IUserDao">
<!-- 查询所有 -->
<select id="findAll" resultType="com.fox.pojo.User">
select * from user;
</select>
</mapper>
jdbc.properties:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=123456
log4j.properties:
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
在webapp文件下创建META-INF目录,并在META-INF目录中建立一个名为context.xml的配置文件:
context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<!--
<Resource
name="jdbc/test" 数据源的名称
type="javax.sql.DataSource" 数据源类型
auth="Container" 数据源提供者
maxActive="20" 最大活动数
maxWait="10000" 最大等待时间
maxIdle="5" 最大空闲数
username="root" 用户名
password="123456" 密码
driverClassName="com.mysql.jdbc.Driver" 驱动类
url="jdbc:mysql://localhost:3306/test" 连接url字符串
/>
-->
<Resource
name="jdbc/test"
type="javax.sql.DataSource"
auth="Container"
maxActive="20"
maxWait="10000"
maxIdle="5"
username="root"
password="123456"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/test"
/>
</Context>
配置 MyBatis 核心配置文件SqlMapConfig.xml,数据源写JNDI:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置properties-->
<properties resource="jdbc.properties"></properties>
<!--配置环境-->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接数据库的必备信息 type属性表示是否使用数据源(连接池)-->
<dataSource type="JNDI">
<property name="data_source" value="java:comp/env/jdbc/test"/><!--java:comp/env/是固定的-->
</dataSource>
</environment>
</environments>
<!-- 配置映射文件的位置 -->
<mappers>
<mapper resource="com/fox/dao/IUserDao.xml" />
</mappers>
</configuration>
配置Tomcat:
在WEB-INF下的index.jsp中写测试代码(因为只有这样才会经过Tomcat服务器 ):
<%@ page import="org.apache.ibatis.io.Resources" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="org.apache.ibatis.session.SqlSessionFactoryBuilder" %>
<%@ page import="org.apache.ibatis.session.SqlSessionFactory" %>
<%@ page import="org.apache.ibatis.session.SqlSession" %>
<%@ page import="com.fox.dao.IUserDao" %>
<%@ page import="com.fox.pojo.User" %>
<%@ page import="java.util.List" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<body>
<h2>Hello World!</h2>
<%
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.根据配置文件构建SqlSessionFactory
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用SqlSessionFactory创建SqlSession对象
SqlSession sqlSession = factory.openSession();
//4.使用SqlSession构建Dao的代理对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
//5.执行dao中的findAll方法
List<User> users = userDao.findAll();
for(User user : users){
System.out.println(user);
}
//6.释放资源
sqlSession.close();
in.close();
%>
</body>
</html>
运行Tomcat:
IDEA输出:
二、MyBatis 的事务控制
在 JDBC 中我们可以通过手动方式将事务的提交改为手动方式,通过 setAutoCommit()
方法就可以调整。那么我们的 Mybatis 框架因为是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身也是用 JDBC 的setAutoCommit()
方法来设置事务提交方式的。
MyBatis是通过sqlsession对象的commit()
方法和rollback()
方法实现事务的提交和回滚。
(一)MyBatis 中手动提交事务方式
手动提交事务:sqlSession.commit();
我们运行之前所写的代码:
import com.fox.dao.IUserDao;
import com.fox.pojo.User;
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.IOException;
import java.io.InputStream;
public class MybatisTest {
private InputStream in = null;
private SqlSession sqlSession =null;
private IUserDao userDao = null;
@Before //标记在非静态方法上。在@Test方法前面执行,而且是在每一个@Test方法前面都执行。
public void init() throws IOException {
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.使用构建者创建工厂对象 SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.使用 SqlSessionFactory 生产 SqlSession 对象
sqlSession = factory.openSession();
//4.使用 SqlSession 创建 dao 接口的代理对象
userDao = sqlSession.getMapper(IUserDao.class);
}
@After //标记在非静态方法上。在@Test方法后面执行,而且是在每一个@Test方法后面都执行。
public void destroy() throws IOException {
//增删改,记得提交事务
sqlSession.commit();
//释放资源
sqlSession.close();
in.close();
}
@Test
public void testAddUser(){
User user = new User();
//由于id是自增的,所以没有id
user.setUserName("小黑");
user.setBirthday(new Date());
user.setUserAddress("上海");
user.setSex("男");
userDao.addUser(user);
}
}
观察在它在控制台输出的结果:
这是我们的 Connection 的整个变化过程,通过分析我们能够发现之前的 CUD 操作过程中,我们都要手动进行事务的提交,原因是在执行前 setAutoCommit()
方法它的值被设置为 false 了,所以我们在 CUD 操作中,必须通过 sqlSession.commit()
方法来执行提交操作。
(二)MyBatis 中自动提交事务方式
通过上面的研究和分析,现在我们一起思考,为什么 CUD 过程中必须使用 sqlSession.commit()
提交事务?主要原因就是在连接池中取出的连接,都会将调用 connection.setAutoCommit(false)
方法,这样我们就必须使用 sqlSession.commit()
方法,相当于使用了 JDBC 中的 connection.commit()
方法实现事务提交。
自动提交事务:SqlSession sqlSession = factory.openSession(true);
import com.fox.dao.IUserDao;
import com.fox.pojo.User;
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.IOException;
import java.io.InputStream;
public class MybatisTest {
private InputStream in = null;
private SqlSession sqlSession =null;
private IUserDao userDao = null;
@Before //标记在非静态方法上。在@Test方法前面执行,而且是在每一个@Test方法前面都执行。
public void init() throws IOException {
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.使用构建者创建工厂对象 SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
/