MyBatis框架
MyBatis框架
支持SQL的持久层框架,使用XML或注解配置
MyBatis3.5.0核心包及第三方支持
中文文档
JDBC的劣势:
- 频繁创建,释放连接
- 硬编码
MyBatis
使用数据库连接池管理数据库连接
使用XML映射文件配置SQL语句
MyBatis工作原理
读取MyBatis的配置文件。
mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。
加载映射文件。
映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
构造会话工厂。
通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory。
创建会话对象。
由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。
Executor执行器。
MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。
MappedStatement对象。
在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。
输入参数映射。
输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。
输出结果映射。
输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。
MyBatis工作原理
使用方法
-
配置文件:SqlMapConfing.xml
<?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 resource="db.properties"/> <!--包别名--> <typeAliases> <package name="cn.edu.xynu.ssm.POJO"/> </typeAliases> <!-- 和spring整合后 environments将废除 --> <environments default="development"> <environment id="development"> <!-- 使用JDBC事务管理,事务控制由mybatis --> <transactionManager type="JDBC"/> <!-- 数据库连接池,由mybatis --> <dataSource type="POOLED"> <!-- com.mysql.cj.jdbc.Driver,5.1以上版本mySqlDriver--> <!-- 注意要引入lib包--> <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> </environment> </environments> <!--映射文件位置--> <mappers> <!--单个文件映射:resource属性一次加载一个文件,指定mapper.xml文件位置, mapper.xml文件通过namespace来查找mapper.java接口文件--> <mapper resource="config/mapper/UserMapper.xml"/> <!--class方式映射:通过mapper接口映射单个文件 遵循规则:mapper.java和mapper.xml放在同一个目录下,且命名相同--> <!-- <mapper class="com.wyscoder.mybatis.mapper.StudentMapper"/>--> <!--批量mapper接口的扫描 遵循规则:mapper.java的接口文件和mapper.xml放在同一目录下,且命名相同--> <!-- <package name="com.wyscoder.mybatis.mapper"/>--> </mappers> </configuration>
db.properties
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/db_mybatis?characterEncoding=utf-8 jdbc.username=root jdbc.password=54931090534
-
映射文件
<?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="cn.edu.xync.ssm.dao.UserMapper"> <!-- 表别名映射--> <resultMap id="UserResultMap" type="TUserEntity"> <id column="idn" property="id"/> <result column="w" property="workId"/> <result column="n" property="name"/> <result column="pw" property="password"/> <!-- <result column="em" property="email"/>--> <!-- <result column="ph" property="phone"/>--> </resultMap> <!-- id与接口文件方法名一致--> <!-- parameterType:表示输入参数的类型--> <!-- resultType:表示输出参数的类型--> <select id="selectUserById" resultType="TUserEntity"> select * from t_user where id = #{id} </select> <select id="findAll" resultMap="UserResultMap"> -- select u.id as idn,u.workId as w,u.name as n,u.password as pw,u.email as em,u.phone as ph from t_user u select u.id as idn,u.name as n,u.workId as w,u.password as pw from t_user u </select> <!--#{占位符},占位符可以随便写--> <insert id="UserSave" parameterType="TUserEntity"> insert into t_user(id,workId,name,password) values (#{id},#{workId},#{name},#{password}) </insert> <!-- parameterType只有一个参数时,参数类型,(可用map传多参,也可以传基本类型,包装类)--> <!--或在接口中使用@param注解--> <update id="UserUpdateById" parameterType="map"> update t_user set name = #{name} where id = #{id} </update> <delete id="UserDeleteById" parameterType="int"> delete from t_user where id = #{id} </delete> <!--UserDTO 包装类--> <select id="findUserByGoodsAndPhone" parameterType="UserDTO" resultType="TUserEntity"> select * from t_user u,t_user_goods ug where u.id = ug.userId and ug.goodsId=#{goods.id} and u.phone like '${user.phone}%' </select> </mapper>
3.会话工厂
package util;
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 java.io.IOException;
import java.io.InputStream;
public class MybatisUtil {
private static SqlSession sqlSession = null;
static { //静态代码段
// 读取全局配置文件
String path = "SqlMapConfig.xml";
// 通过Resources获取配置文件流
InputStream resourceAsStream = null;
try {
resourceAsStream = Resources.getResourceAsStream(path);
} catch (IOException e) {
e.printStackTrace();
}
// 创建会话工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 创建会话
sqlSession = sqlSessionFactory.openSession();
}
public static SqlSession getSqlSession(){
return sqlSession;
}
}
4:test
- @Before:该标签函数在每个Test前执行
- @After:每个Test后执行
- @BeforeClass 使用此注解的方法在测试类被调用之前执行
- @AfterClass 使用此注解的方法在测试类被调用结束退出之前执行
获取sql对象
private SqlSession sqlSession = MybatisUtil.getSqlSession();
代理模式:
- Mybatis 读取XML配置文件后会将内容放在一个Configuration类中,SqlSessionFactoryBuilder会读取Configuration类中信息创建SqlSessionFactory。
- 在初始化SqlSessionFactory时,Mapper 接口进行注册,注册在了名为 MapperRegistry 类的 HashMap中,
- key = Mapper class, value = 创建当前Mapper的工厂。
- SqlSessionFactory创建SqlSession。
- SqlSession中可以通过getMapper()拿到代理对象,SqlSession.getMapper 运用了 JDK动态代理,产生了目标Mapper接口的代理对象。
- 动态代理的 代理类是 MapperProxy ,这里边mapperMethod.execute(sqlSession, args)最终完成了增删改查方法的调用。
@Test
public void selectUserById() {
// 代理模式创建代理类
//通过getMapper方法使用动态代理实现对应的mapper接口实现类的对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
TUserEntity User = mapper.selectUserById(1);
System.out.println(User);
}
原始调用:
@Test
public void findAll(){
// 原始调用
//未明确接口标签时,使用全路径
List<TUserEntity> users = sqlSession.selectList("cn.edu.xync.ssm.dao.UserMapper.findAll");
for (TUserEntity s:users){
System.out.println(s);
}
}
动态SQL
if
<!--UserDTO 包装类-->
<select id="findUserByPhoneAndEmail" parameterType="UserDTO" resultType="TUserEntity">
select * from t_user u
<where>
<if test="user!=null">
<if test="user.email!=null and user.email=!''">
and u.email like '%${user.email}'
</if
</if>
<if test="user!=null">
<if test="user.phone!=null and user.phone=!''">
and u.phone like '%${user.phone}'
</if>
</if>
</where>
<!--where u.id = ug.userId and ug.goodsId=#{goods.id} and u.phone like '${user.phone}%' -->
</select>
SQL片段
<!--必须有一个唯一ID-->
<sql id = user_query_statement>
<if test="user!=null">
<if test="user.email!=null and user.email=!''">
and u.email like '%${user.email}'
</if>
</if>
<if test="user!=null">
<if test="user.phone!=null and user.phone=!''">
and u.phone like '%${user.phone}'
</if>
</if>
</sql>
<select id="findUserByPhoneAndEmail" parameterType="UserDTO" resultType="TUserEntity">
select * from t_user u
<where>
<!--使用include包含-->
<include refid="user_query_statement"></include>
</where>
<!--where u.id = ug.userId and ug.goodsId=#{goods.id} and u.phone like '${user.phone}%' -->
</select>
forEach片段
<sql id = user_query_statement>
<if test="user!=null">
<if test="user.email!=null and user.email=!''">
and u.email like '%${user.email}'
</if>
</if>
<if test="user!=null">
<if test="user.phone!=null and user.phone=!''">
and u.phone like '%${user.phone}'
</if>
</if>
<if test="ids!=null">
<!--collection 和dto对应 item 临时中间变量 open 循环体开始 separator 间隔符-->
<foreach collection="ids" item = "user_id" open = "and (" close=")" separator="or">
id=#{user_id}
</foreach>
</if>
<if test="ids!=null">
<!--collection 和dto对应 item 临时中间变量 open 循环体开始 separator 间隔符-->
<foreach collection="ids" item = "user_id" open = "and id in(" close=")" separator=",">
id=#{user_id}
</foreach>
</if>
</sql>
<!--ids属于UserDTO的属性-->
<select id="findUserByPhoneAndEmail" parameterType="UserDTO" resultType="TUserEntity">
select * from t_user u
<where>
<!--使用include包含-->
<include refid="user_query_statement"></include>
</where>
<!--where u.id = ug.userId and ug.goodsId=#{goods.id} and u.phone like '${user.phone}%' -->
</select>
高级映射-业务模型扩展
主键,外键,关系数据库
引用数据库案例
高级映射-ResultType实现一对一关系映射
以resultType实现的映射,需要创建对应的POJO/DTO
<!--在查询标签中写入对应sql的resultType路径-->
<select id="selectUserById" resultType="TUserEntity">
<!--此处是SqlMapConfing.xml中定义了包别名,故上方不必添加具体路径-->
<!--包别名-->
<typeAliases>
<package name="cn.edu.xynu.ssm.POJO"/>
<package name="cn.edu.xynu.ssm.DTO"/>
</typeAliases>
高级映射-ResultMap实现一对一关系映射
需要在对应的实体类中添加关联的实体生成get set方法,重写tostring();
<resultMap id="OrderUserResultMap" type="Oreder2">
<id column="id" property="id"/>
<result column="user_id" property="user_id"/>
<result column="number" property="number"/>
<result column="create_time" property="create_time"/>
<!--association映射关联的属性//一对一-->
<association property="user" javaType="User">
<id column="user_id" property="id"/>
<result column="worId" property="workId"/>
<result column="name" property="name"/>
</association>
</resultMap>
高级映射-ResultType和ResultMap的区别
使用type需要添加新的类,但使用简单
使用Map需要在实体中添加关联的实体,写出对应的ResultMap,用 < a s s o c i a t o n g > <associatong> <associatong>关联,但更加灵活
高级映射-ResultMap实现一对多关系映射
在一对多关系中,给1的对象实体添加list类型的多实体对象,生成get set方法,重写tostring();
<resultMap id="OrderAndOrderDetailResultMap" type="Oreder2">
<id column="id" property="id"/>
<result column="user_id" property="user_id"/>
<result column="number" property="number"/>
<result column="create_time" property="create_time"/>
<!--collection映射关联的list属性//一对多-->
<collection property="orderDetail" ofType="OrderDetail">
<id column="orderDetail_id" property="orderDetail_id"/>
<result column="order_id" property="order_id"/>
<result column="goods_id" property="goods_id"/>
<result column="goods_number" property="goods_number"/>
</collection>
</resultMap>
resultMap可以被继承
<resultMap id="OrderAndOrderDetailResultMap" type="Oreder2" extends="OrderUserResultMap">
<!--collection映射关联的list属性//一对多-->
<collection property="orderDetail" ofType="OrderDetail">
<id column="orderDetail_id" property="orderDetail_id"/>
<result column="order_id" property="order_id"/>
<result column="goods_id" property="goods_id"/>
<result column="goods_number" property="goods_number"/>
</collection>
</resultMap>
高级映射-ResultMap实现多对多关系映射
找准主表及其关联表,对应的sql语句表头定义别名
将用户信息映射到UserInfo中;
在UserInfo中添加订单列表属性(List orders);将用户创建的订单映射到orders;
在Order中添加订单明细列表属性(List orderDetails);将订单对应的订单明细映射到orderDetails;
在OrderDetail中添加商品属性(GoodsInfo goodsInfo);将订单明细对应的商品映射到goodsInfo中。
生成对应get()set(),重写tostring
<select id="findUserAndGoodsResultMap" resultMap="userAndGoodsResultMap">
SELECT
u.id u_id,
u.workId,
u.name,
o.id o_id,
o.user_id o_user_id,
o.create_time o_create_time,
od.order_id od_order_id,
od.orderdetail_id,
g.id g_id,
g.name g_name,
g.price
FROM
t_user u,
t_order o,
t_orderdetail od,
t_goods g
WHERE u.id = o.user_id
AND od.order_id = o.id
AND od.goods_id = g.id
</select>
定义对应的resultMap
<resultMap type="edu.xync.pro.entity.UserInfo" id="userAndGoodsResultMap">
<!--
(1) 配置映射的订单信息
id:指定查询结果中的唯一标识,也就是用户表的唯一标识(如果唯一标识由多列构成,需要配置多个id)
column:订单信息的唯一标识列名
property:对应POJO类中的属性名称
-->
<id column="u_id" property="id"/>
<result column="workId" property="workId"/>
<result column="name" property="name"/>
<!--
(2)配置映射关联的订单信息
一个用户可以创建多个订单,使用collection
collection:将多个关联查询出的记录映射到集合对象中
property:表示关联查询的订单关联到UserInfo中的哪个属性
ofType:表示映射到的集合中所指定的POJO类型
-->
<collection property="orders" ofType="edu.xync.pro.entity.Order">
<!--
id:订单的唯一标识
column:订单唯一标识的名称
property:对应到订单对应的POJO中的哪个属性
-->
<id column="o_id" property="id"/>
<result column="o_user_id" property="user_id"/>
<result column="o_create_time" property="create_time"/>
<!--
(3)配置映射关联的订单明细信息
一个订单包含多个订单明细,使用collection
collection:将多个关联查询出的记录映射到集合对象中
property:表示关联查询的订单明细关联到Orders中的哪个属性
ofType:表示映射到的集合中所指定的POJO类型
-->
<collection property="orderDetails" ofType="edu.xync.pro.entity.OrderDetail">
<!--
id:订单明细的唯一标识
column:订单明细唯一标识的名称
property:对应到订单明细对应的POJO中的哪个属性
-->
<id column="orderdetail_id" property="orderdetail_id"/>
<result column="od_order_id" property="order_id"/>
<!--
(4)配置映射关联的商品信息
association:用于映射关联查询单个的对象
property:表示关联查询的商品信息关联到OrderDetail中的哪个属性
-->
<association property="goodsInfo" javaType="edu.xync.pro.entity.GoodsInfo">
<!-- id:关联查询的商品对象的唯一标识
column:指定唯一标识商品信息的列
property:对应到商品信息的POJO中的哪个属性
-->
<id column="g_id" property="id"/>
<result column="g_name" property="name"/>
<result column="price" property="price"/>
</association>
</collection>
</collection>
</resultMap>
多表查询,需要定义别名,以保证不会混淆,嵌套的使用collection和association,关联对象
高级映射-ResultType和ResultMap
resultType:
查询结果与POJO属性一一映射。单表使用最佳
一个Type一个类
resultMap:
使用association和collection完成一对一和一对多高级映射。
association完成关联信息映射到一个POJO对象中。
collection完成关联信息映射到一个List集合中。
定义好与数据库对应的POJO后依照表关系添加合适的关联属性,私以为适用于常见的大多数多表查询.
同时具备延迟加载的功能
使用association实现延迟加载功能
正常书写查询语句
在ResultMap中使用assocation 通过select 和column属性指定延迟加载的查询语句及其参数
<!-- 延迟加载用户的resultMap -->
<resultMap type="edu.xync.pro.entity.Order" id="orderAndUserLazyLoading">
<!-- 对订单信息进行映射配置 -->
<id column="id" property="id"/>
<result column="user_id" property="user_id"/>
<result column="number" property="number"/>
<result column="create_time" property="create_time"/>
<result column="note" property="note"/>
<!-- 实现对用户信息延迟加载
select:指定延迟加载需要执行的statement的id
使用UserMapper.xml中statement为findUserById完成对用户信息的查询,不在同一个命名空间的话,需要加namespace名称
column:订单信息中需要关联用户信息需要查询的列,是user_id
-->
<association property="userInfo" javaType="edu.xync.pro.entity.UserInfo" select="edu.xync.pro.dao.mapper.UserMapper.findUserById" column="user_id">
</association>
</resultMap>
SqlMapConfig.xml配置文件中配置好延迟加载开关
<settings>
<!-- 打开延迟加载的开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 关闭积极加载改为消极加载,等到执行get方法时再去延迟加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
测试中直接调用关联的属性的get方法即可获取到延迟加载的信息
缓存
缓存中有查询语句对应的数据时,就不用再访问数据库了
一级缓存
sqlSession,操作数据库时创建sqlSession对象,内部有一个HashMap用户存储数据,不同的sqlsession有各自的HashMap
二级缓存
Mapper多个selSession使用同一个Mapper的缓存
一级缓存
MyBatis默认开启一级缓存,无需额外修改配置
当SqlSession执行了commit操作(插入,删除,更新),清空一级缓存,保持缓存与数据库一致,避免脏数据
二级缓存
MyBatis默认关闭二级缓存
二级缓存范围更大,多个sqlsession可以共享一个mapper下的二级缓存。每一个namespace下的mapper都有一个二级缓存区域,如果两个不同的mapper具有相同的namespace,则这两个mapper执行查询操作将数据存放到同一个二级缓存中。
配置文件
SqlMapConfig.xml
<settings>
<!-- 打开延迟加载的开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 关闭积极加载改为消极加载,等到执行get方法时再去延迟加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
Mapper.xml
<mapper namespace="edu.xync.pro.dao.mapper.UserMapper">
<!--将对应的mapper映射文件开启二级缓存-->
<cache/>
对应的实体类需要实现序列化接口Serializable,并生成默认版本号:,使当前实体采用默认的序列化方式,接口本身是一个空接口,相当于一个标记
Commit清空缓存
useCache
设置为false,不使用缓存,每次查询数据库
useCache默认为true,表示会将本条语句的结果进行二级缓存。
问题:设置为false时,是否会将数据存入Sqlsession?
<select id="findUserById" parameterType="java.lang.Integer" resultType="UserInfo" useCache="false">
select * from t_user where id=#{id}
</select>
flushCache
默认刷新缓存
flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存。
在insert、update、delete语句时: flushCache默认为true , 为false就不会刷新缓存
那么使用缓存时手动修改了数据表中的数据,缓存没有刷新,就会出现脏读。
Ehcache:轻量式分布式缓存
MyBatis与Ehcache整合包的下载地址:
http://www.java2s.com/ref/jar/download-mybatisehcache110jar-file.html
用到的是两个jar包** ehcache-core-2.6.6.jar和mybatis-ehcache-1.1.0.jar**。
配置mapper中cache的type为Ehcache
<mapper namespace="edu.xync.pro.dao.mapper.UserMapper">
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
在classpath中加入Ehcache的配置文件。在config目录下创建ehcache.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\\ehcache" />
<defaultCache
maxElementsInMemory="10000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
我是片尾
笔记有限而学无止境
诸君共勉