一、MyBatis入门
关于Mybatis入门相关的知识,我相信没有人比官方文档更详细了,详见中文官方文档,新手半天时间即可入门。
二、MyBatis执行流程
1.将sql语句和数据库配置信息保存在XML配置文件
2.在MyBatis运行时,将配置信息存储Configuration对象【因为SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory的实例(实际为DefaultSqlSessionFactory),不管是自己创建Configuration 构建器还是通过读取xml构建Configuration对象,本质上都是把配置信息存储Configuration对象中】
3.通过SqlSessionFactory创建SqlSession对象提供属性
1) autoCommit = false 是否自动提交
2) Configuration对象
3) dirty:true sql语句执行完毕后 可以事务提交
false sql语句执行发送错误 事务进行回滚(一开始为false,在执行sql语句后改为true)
4) Executor执行器对象:
创建Statement对象,在创建过程中依靠MapperStatement对象将赋值内容与sql占位符进行绑定
4.此时获得SqlSession即可以对数据库进行操作
疑问: 如代码 sqlSession.insert("insertProduct",Product)
<mapper namespace="Product">
<insert id = "insertProduct">
INSERT INTO product(pname,price) VALUES(#{pname},#{price})
</insert>
</mapper>
(1)mybatis是如何将sql语句的id与xml中的sql语句进行对应呢?
答: MappedStatement ms = configuration.getMappedStatement(statement); //在构建Configuration,存在一个HashMap,key为xml中的namespace+id值,如上述例子所示的话key即为"Product.insertProduct",value为MappedStatement对象。这个MappedStatement对象中有几个重要的属性,如id即为"Product.insertProduct",resource为"ProductMapper.xml",sqlSource属性中存储着具体要执行的sql语句"INSERT INTO product(pname,price) VALUES(?,?)"
(2)mybatis如何将实体类数据绑定到sql语句中呢?
<insert id = "userSave">
insert into user values(#{userId},#{username},#{job})
</insert>
首先所有的namespace不能为null或"",把所有的#{}都换为占位符? ,后把参数名"userId"、"username"、"job"三个参数名放到一个ArrayList集合里面【顺序需要与sql参数顺序一致】,在使用时候使用反射机制从当前对象中读取属性进行反射赋值。
用户传递一个装载着数据的实体对象,如何把这个实体对象添加到上述的占位符中呢?首先它会判断这个实体对象是集合,是否是数组,如果都不是则直接返回这个实体对象继续处理。
在执行完prepareStatement(...)方法后,实体数据封装到了sql语句占位符中,实际上这个stmt的真实类型为JDBC4PreparedStatement对象,在后续代码中又被强转为了PreparedStatement
(3)mybatis如何传输sql语句到数据库呢?
boundSql内部存储了sql属性(存放着sql语句),还有parameterMappings(读取实体类的哪个属性,从哪读取等信息)
接合上述的三个问题,不难看出,Mybatis就是对jdbc的良好封装!!!
5.SqlSession.commit(); 根据此时dirty属性决定提交和回滚
底层最终调用的是connection.commit();
6.SqlSession.close(); 关闭SqlSession,实际上是将connection放回连接池中
三、Mybatis自定义类型转换器
下面我们将来自定义我们的类型转换器,参照《mybatis官方文档——类型转换器TypeHandler》
Mybatis中的核心配置mybatis-config.xml标签都对应一个接口,而接口下又会有若干的实现类针对不同的情况进行相应的处理,mybatis允许我们对某些接口的实现类进行重新定义,这是mybatis的设计理念,当mybatis的内置接口实现类不能满足需求时,则开发者可以自己声明一个实现类,就如我们正要说的TypeHandler类型转换器。
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集(ResultSet)中取出一个值时, 都会用类型处理器(TypeHandler)处理数据库类型与Java类型之间的转换。
比如有这么一个业务场景,在mysql部门表有包含一个字段flag(int类型)
但在Java实体类Dept部门类中含有一个boolean flag属性,此时Java Types(boolean)和JDBC Types(int)并不对应。
由上标可知,Java中的boolean类型就只和数据库中的boolean类型对应,并不会帮我们转换成int类型作为解析,此时就需要我们定义一个类型转换器规则来告诉Mybatis,当将Java Types中的boolean转换为JDBC Types中的int,就是用我们自定义的类型转换器,做法如下:
(1)创建自定义类型转换器MyTypeHandler实现TypeHandler接口
/*
* setParameter方法:
* 在生成SQL语句时被调用
* getResult:
* 查询结束之后,在将ResultSet数据行装换为实体类对象时
* 通知TypeHandler将当前数据行某个字段转换为何种类型
* */
public class MyTypeHandler implements TypeHandler {
// 将被生成的PreparedStatement对象,i表示占位符的位置,parameter代表所给的参数(即Dept实体类中的flag属性)
// 为SQL语句占位符赋值操作
public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {// dept.flag=null insertsql flag设置0(根据业务灵活设置)
ps.setInt(i, 0);
return;
}
System.out.println("类型转换器开始工作");
Boolean flag = (Boolean) parameter;
if (flag == true) {
ps.setInt(i, 1);
} else {
ps.setInt(i, 0);
}
}
public Object getResult(ResultSet rs, String columnName) throws SQLException {
int flag = rs.getInt(columnName);
Boolean myFlag = Boolean.FALSE;
if (flag == 1) {
myFlag = Boolean.TRUE;
}
//返回后会将值设置到columnName所对应的实体类对象
return myFlag;
}
public Object getResult(ResultSet rs, int columnIndex) throws SQLException {
return null;
}
public Object getResult(CallableStatement cs, int columnIndex) throws SQLException {
return null;
}
}
(2)在MyBatis核心配置文件mybatis-config.xml注册自定义类型转换器
注意标签是有先后顺序的!!!
<typeHandlers>
<!-- 声明自定义类型转换器的位置 当Java类型为Boolean,对应jdbcType为NUMERIC类型时,使用自定义转换器自动转换 -->
<typeHandler handler="cn.itcats.utils.MyTypeHandler"
javaType="Boolean" jdbcType="NUMERIC" />
</typeHandlers>
(3)在Mapper.xml文件中指定使用自定义类型转换器场合
<resultMap type="Dept" id="DeptResultMap">
<result column="flag" property="flag"
typeHandler="cn.itcats.utils.MyTypeHandler" />
</resultMap>
(4)在查询Statement中指定对应的ResultMap
<select id="deptFindAll" resultMap="DeptResultMap">
select * from dept
</select>
四、Mybatis自定义对象工厂(objectFactory)
MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。 如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。比如存在该业务场景:
Dept类中包含字段country,而数据库中无对应的country字段,那么在查询数据库时候生成的对应实体类对象中country字段应为null,而业务需要该实体类对象应为"China",如果每次查询再set进去会十分麻烦,这时就可以使用对象工厂。
(1)书写自定义工厂类继承与DefaultObjectFactory
因继承DefaultObjectFactory的原因,可以利用父类中super.create(type)方法帮助我们创建一个实例对象
public class MyObjectFactory extends DefaultObjectFactory {
public Object create(Class type) {// 重新定义Dept类实例对象创建规则,其他类实例对象创建规则不想改变
if (Dept.class == type) {
// 依靠父类提供create方法创建Dept实例对象
Dept dept = (Dept) super.create(type);
// 设置自定义规则
dept.setCountry("China");
return dept;
}
return super.create(type);
}
}
(2)在MyBatis核心文件mybatis-config.xml中注册自定义工厂
<objectFactory type="cn.itcats.util.MyObjectFactory"></objectFactory>
(3)测试查询方法返回的Dept实体类country属性是否为"China"
五、plugins拦截器