目录
1.简介
Mybatis 是一款数据持久化框架, 内部封装了 JDBC,简化了加载驱动、创建连接、创建 statement 等繁杂的过程,并且也增加了一些高级特性,可以替代JDBC。一般只需要关注 SQL 语句本身, 想要使用Mybatis框架,导入mybatis-x.x.x.jar 和 mysql-connector-java-x.x.x.jar包就可以
2.入门
2.1基本配置
该配置文件包含对Mybatis应用的核心配置, 包括获取数据库连接实例的数据源DataSource, 以及决定事务作用域和控制方式的事务管理器(TransactionManager), 简单示例:
<?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>
<!-- 后面说明 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- mappers标签用于注册绑定我们的Mapper映射文件,告诉 MyBatis 到哪里去找到这些SQL语句,下面三种方式根据个人选择 -->
<mappers>
<!-- 通过类路径下资源引用,绑定Mapper映射文件,如果提示没找到,可以在out输出目录看一下是否存在 -->
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
<!-- 任意位置Mapper映射文件,前面file:///固定写法,表示协议 -->
<mapper url="file:///C:\Users\eaeyon\Desktop\DogMapper.xml" />
<!-- 直接使用映射接口的完全限定类名,但是其对应的Mapper映射文件需要在同包下,并且名字一样 -->
<mapper class="dao.DogDao" />
</mappers>
</configuration>
该配置文件是Mapper的映射配置文件
<?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="dao.DogDao">
<select id="queryOne" resultType="dao.Dog">
select * from Dog where id = #{id}
</select>
</mapper>
2.2核心类
每个基于Mybatis的应用都是以一个SqlSessionFactory实例为核心,SqlSessionFactory的实例可以通过SqlSessionFactoryBuilder对象创建
1.SqlSessionFactoryBuilder:
用于创建SqlSessionFactory,有很多重载方法 , 传入的参数就是mybatis的配置文件, 创建完成后SqlSessionFactoryBuilder就不使用了
a.通过mybatis.xml配置文件创建
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
b.通过Configuration配置类创建
//数据源
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
//事务管理器
TransactionFactory transactionFactory = new JdbcTransactionFactory();
//环境
Environment environment = new Environment("development", transactionFactory, dataSource);
//配置类
Configuration configuration = new Configuration(environment);
//BlogMapper接口中包含SQL映射注解, 从而避免依赖xml文件
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
2.SqlSessionFactory:
用于创建SqlSession, 有很多重载方法 , 选择自己合适的就可以 , 一般Mybatis应用中创建一个SqlSessionFactory 实例就可以了,并且一直存在, 可以使用单例模式或者静态单例模式
public class Main {
public static void main(String[] args) throws Exception{
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session1 = sqlSessionFactory.openSession();
System.out.println(session1); //org.apache.ibatis.session.defaults.DefaultSqlSession@27f674d
SqlSession session2 = sqlSessionFactory.openSession();
System.out.println(session2); //org.apache.ibatis.session.defaults.DefaultSqlSession@1d251891
}
}
3.SqlSession:
SqlSession提供在数据库执行SQL命令的所有方法, 每个线程都应该有它自己的 SqlSession 实例,因此它的最佳的作用域是请求或方法作用域, 并且记得关闭
SqlSession也提供了javax.sql.Connection中相关方法 , 但实际执行的是它里面的Executor接口对象 , 而Executor中实际执行的是org.apache.ibatis.transaction.Transaction
所以SqlSession进行事务管理使用的是Transaction接口实现类 , SqlSession的getConnection()方法获取的Connection也是间接从Transaction接口实现类中获取 , 其实现类通过javax.sql.DataSource获取, 如果DataSource实现类使用了连接池技术, 就从池中获取
4.映射器实例:
映射器接口的实例从 SqlSession 中获得
2.3案例
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//获取SqlSession
try (SqlSession session = sqlSessionFactory.openSession()) {
//获取映射器实例,调用方法执行SQL语句----推荐
DogDao dogDao = session.getMapper(DogDao.class);
Dog dog = dogDao.queryOne(1007);
//直接调用SqlSession中方法 执行SQL语句----不推荐
Dog dog1 = session.selectOne("dao.DogDao.queryOne", 1008);
System.out.println(dog);
}
3.Mybatis配置文件-相关配置
<properties> 引入外部配置文件,通过${}表达式使用
user=root
password=123456
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
driverClass=com.mysql.jdbc.Driver
<configuration>
<properties resource="jdbc.properties" >
<!-- 启用默认值特性 -->
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driverClass}"/>
<property name="url" value="${url}"/>
<!-- 从 MyBatis 3.4.2 开始,你可以为占位符指定一个默认值 -->
<!-- 用户名不对报错: Cause: java.sql.SQLException: Access denied for user '${myname}'@'localhost' (using password: YES) -->
<!-- 配置文件中没有myname对应属性,可以设置默认值 -->
<!-- <property name="username" value="${myname}"/> -->
<property name="username" value="${myname:root}"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
</configuration>
<settings>极为重要的调整设置
<configuration>
<settings>
<!-- 开启或关闭全局已配置的缓存,默认true -->
<setting name="cacheEnabled" value="true"/>
<!-- 延迟加载的全局开关,默认false -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
.........
</settings>
</configuration>
<typeAliases>类型别名
<typeAliases>
<!-- Author可以用在任何使用 com.baidu.Author 的地方 -->
<typeAlias alias="Author" type="com.baidu.Author"/>
<!-- 也可以指定一个包, 将包中所有Java Bean的类名首字母小写作为别名-->
<package name="com.baidu"/>
</typeAliases>
在Mybatis中,也有一些Java类型内建的类型别名,如
byte---->_byte Byte---->byte
int---->_int或者_integer Integer---->int或者integer
<typeHandlers>类型处理器
类型处理器用于PreparedStatement中参数设置或者从结果集ResultSet中取出一个值时转换为对应java类型
public interface TypeHandler<T> {
//设置PreparedStatement的占位符参数, 不同的类型处理器调用PreparedStatement的不同类型的设置方法
//index表示要设置的位置 , param表示要设置的参数
void setParameter(PreparedStatement ps, int index, T param, JdbcType var4) throws SQLException;
//下面是将从ResultSet结果集中获取的值,转换为对应java类型,
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement var1, int var2) throws SQLException;
}
分析IntegerTypeHandler
SQL语句:
<select id="queryOne" resultType="com.company.entity.Dog">
select * from Dog where id = #{id} and name = #{name}
</select>
Dog dog = dogDao.queryOne(1022,"cc");
第一个参数int类型, 经过一系列判断, 最后选择了IntegerTypeHandler , 然后执行他的 setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType);
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
if (jdbcType == null) {
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException var7) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. Cause: " + var7, var7);
}
} else {
try {
//因为此时传的参数不为空,所以通过这个方法来设置
this.setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception var6) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . Try setting a different JdbcType for this parameter or a different configuration property. Cause: " + var6, var6);
}
}
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
ps.setInt(i, parameter);
}
所以PreparedStatement设置int类型参数时使用的是IntegerTypeHandler ; 第二个参数选择的是StringTypeHandler , 执行流程都是差不多的
因为上面是查询,结果会封装为Dog对象,所以会调用IntegerTypeHandler 和 StringTypeHandler中的getResult(ResultSet rs,Strng columnName)进行转换;
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
T result;
try {
//第一列: 实际getNullableResult()方法里面调用的是rs.getInt(columnName); columnName="id"
//第一列: 实际getNullableResult()方法里面调用的是rs.getString(columnName); columnName="name"
result = getNullableResult(rs, columnName);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e);
}
if (rs.wasNull()) {
return null;
} else {
return result;
}
}
自定义类型处理器,实现TypeHandler接口,或者继承BaseTypeHandler<T>类 , 泛型表示要处理的java类型,将她正确设置到PreparedStatement占位符,以及将从结果集获取的数据转换为该类型
//@MappedTypes(String.class)
//@MappedJdbcTypes(JdbcType.VARCHAR)
public class MyTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i,parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
}
<typeHandlers>
<!-- 添加自定义的类型处理器 -->
<typeHandler handler="org.mybatis.example.MyTypeHandler"/>
</typeHandlers>
注意:
指定管理的Java类型:
1.BaseTypeHandler<T>指定泛型
2.typeHandler元素设置javaType="String"
3.类上添加@MappedTypes(String.class)注解指定
指定管理的JDBC类型:
1.typeHandler元素设置jdbcType="VARCHAR"
2.类上添加@MappedJdbcTypes(JdbcType.VARCHAR)注解指定
再次执行时,PreparedStatement设置String类型占位符时,就会选择MyTypeHandler
<objectFactory>对象工厂
public interface ObjectFactory {
/**
* Sets configuration properties.
* @param properties configuration properties
*/
void setProperties(Properties properties);
/**
* 用默认构造器创建对象
*/
<T> T create(Class<T> type);
/**
* 用指定构造器创建对象 , 传入对应参数
*/
<T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
<T> boolean isCollection(Class<T> type);
}
每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作, 我们可以通过继承DefaultObjectFactory类创建自定义的对象工厂:
public class MyObjectFactory extends DefaultObjectFactory {
//处理无参构造方法
@Override
public Object create(Class type) {
System.out.println("无参构造Class Type : "+type);
return super.create(type);
}
//处理带参数构造方法
@Override
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
System.out.println("有参构造Class Type : "+type);
System.out.println("ParamType List : "+type);
System.out.println("ParamValue List : "+type);
return super.create(type, constructorArgTypes, constructorArgs);
}
//用于配置ObjectFactory,mybatis.xml中其定义的属性会传递给该方法
@Override
public void setProperties(Properties properties) {
System.out.println("获取属性: "+properties);
}
}
<objectFactory type="com.company.config.MyObjectFactory">
<property name="name" value="zhangsan"/>
<property name="age" value="12"/>
</objectFactory>
执行结果:
<plugins>插件 允许你在映射语句执行过程中的某一点进行拦截调用,暂时了解
<environments>元素定义了如何配置环境 重要!!!
可以配置多个环境,但是每个SqlSessionFactory实例只能选择一种环境,比如需要连接两个数据库,就需要创建两个SqlSessionFactory实例
需要指定哪种环境,可以将他作为参数传递给SqlSessionFactoryBuilder
<environments default="development">
<!-- 定义环境的id,默认使用的环境id default="development" -->
<environment id="development">
<!-- 事务管理器的配置: 即之前提到的Transaction接口 -->
<transactionManager type="JDBC">
<!-- 设置属性,会被传入他的setProperties(Properties)方法,和ObjectFactory类似 -->
<property name="..." value="..."/>
</transactionManager>
<!-- 数据源的配置 -->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
事务管理器
在Mybatis中,有两种类型的事务管理器,type="JDBC|MANAGED"
JDBC:
JDBC是别名,实际是Mybatis中的JdbcTransactionFactory工厂类, 该工厂类用来创建JdbcTransaction 事务管理器, 这个管理器直接使用了JDBC的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
MANAGED:
MANAGED是别名,实际是Mybatis中的ManagedTransactionFactory工厂类, 该工厂类用来创建ManagedTransaction事务管理器, 但该管理器什么也没做,而是让容器来管理事务的整个生命周期,默认情况下,他会关闭连接,然而一些容器并不希望一些连接被关闭,所以需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为 , 他的部分源码:
public class ManagedTransaction implements Transaction {
//省略部分...........
@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();
}
}
}
<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>
一般情况下,这两种事务管理器都不需要设置任何属性,他们都是类型别名,换句话说,你可以使用TransactionFactory接口实现类代替他们, 因为他们也是实现的TransactionFactory接口
public interface TransactionFactory {
void setProperties(Properties props);
//从存在的Connection,创建一个Transaction接口的实现类,或者使用第三方的
Transaction newTransaction(Connection conn);
//从数据源创建事务
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
/**
* 管理事务: 包装一个数据库连接,处理连接的生命周期, 提交/回滚和关闭等。
*/
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
Integer getTimeout() throws SQLException;
}
使用这两个接口,你可以完全自定义Mybatis对事务的处理
数据源(dataSource)
用于连接数据库,简化以前jdbc获取连接的繁琐步骤,分为 "有连接池" 和 "无连接池" 的数据源
Mybatis中有三种内建的数据源类型(即type="UNPOOLED POOLED JNDI"):
UNPOOLED: 通过UnPooledDataSourceFactory工厂类创建UnpooledDataSource实例, 这个数据源的实现会每次请求时打开和关闭连接
POOLED: 通过PooledDataSourceFactory工厂类创建PooledDataSource实例, 利用池的概念, 避免创建连接时的时间,常见属性有
poolMaximumActiveConnections – 活动连接数,默认值:10
poolMaximumIdleConnections – 空闲连接数。
JNDI : MyBatis会从JNDI服务上查找DataSource实例,然后返回使用, 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用
不用上面两种数据源 , 也可以通过实现DataSourceFactory接口来使用第三方数据源
public interface DataSourceFactory {
void setProperties(Properties props);
//自己实现DataSource接口,或使用第三方的
DataSource getDataSource();
}
<!-- 比如使用C3P0数据源,连接至 PostgreSQL 数据库 -->
<dataSource type="org.myproject.C3P0DataSourceFactory">
<property name="driver" value="org.postgresql.Driver"/>
<property name="url" value="jdbc:postgresql:mydb"/>
<property name="username" value="postgres"/>
<property name="password" value="root"/>
</dataSource>