Mybatis是什么
- Mybatis是一个持久层的框架,是apache下的顶级项目。myBatis托管到goolecode下,再后来托管到github下
- Mybatis让程序主要精力放在sql上,通过mybatis提供的映射方式,自由靓货色横撑(半自动化,大部分需要程序员编写sql)满足需要sql语句
- mybatis可以将想preparedStatement中的输入参数自动进行输入
Mybatis框架
sqlMapConfig.xml(是mybatis的全局配置文件)
配置了数据源、事务等mybatis运行环境
配置了映射文件(配置sql语句)
mapper.xml(映射文件)、mapper.xml、mapper.xml
| |
\ /
\/
sqlSessionFactory(会话工厂)
作用:创建SqlSession
| |
\ /
\/
sqlSession(会话),是一个面向用户的接口
作用:操作数据库(发出sql增、删、改、查)
| |
\ /
\/
Executor(执行器),是一个接口(基本执行器、缓存执行器)
作用:SqlSession内部通过执行器从操作数据库
| |
\ /
\/
mapped statement(底层封装对象)
作用:对操作数据库存储封装,包括sql语句,输入参数、输出结果类型
| |
\ /
\/
MySQL
入门程序
需求
- 根据用户id(主键)查询用户信息
- 根据用户名称模糊查询用户信息
- 添加用户
- 删除用户
- 更新用户
根据用户id(主键)查询用户信息
全局配置文件
<?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="mysql.properties"></properties>
<!-- 配置mybatis运行环境 -->
<environments default="cybatis">
<environment id="cybatis">
<!-- type="JDBC" 代表使用JDBC的提交想·和回滚来管理事务 -->
<transactionManager type="JDBC" />
<!-- mybatis提供了3种数据源类型,分别是:POOLED,UNPOOLED,JNDI -->
<!-- POOLED 表示支持JDBC数据源连接池 -->
<!-- UNPOOLED 表示不支持数据源连接池 -->
<!-- JNDI 表示支持外部数据源连接池 -->
<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>
</environment>
</environments>
<!-- 加载映射文件 -->
<mappers>
<mapper resource="first/User.xml"></mapper>
</mappers>
</configuration>
映射文件
User.xml(原始ibatis命名),mapper代理开发映射文件名称叫xxxMapper.xml,比如UserMapper.xml、itemsMapper.xml
- 在映射文件中配置sql语句
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace命名空间,作用就是对sql进行分类化管理,理解sql隔离
注意:使用mapper代理方法开发,namespace有特殊重要的作用
-->
<mapper namespace="test">
<!-- 在映射文件中配置很多sql语句 -->
<!-- 需求:通过 -->
<!-- 通过select执行数据库查询
id:标识映射文件中的sql,称为statement的id
将sql语句封装到mappedStatement对象中,所以将id成为statement的id
parameterType:指定输入参数的类型,这里指定int型
#{}表示一个占位符号
#{id}:其中id表示输入的参数,参数名称就是id,如果输入参数是简单类型,#{}中的参数名可以任意
resultType:指定sql输出结果的所映射的java对象类型,select指定resultType表示将单条记录映射成的java对象
-->
<select id="findUserById" parameterType="int" resultType="entity.User">
SELECT * FROM user WHERE id=#{id}
</select>
</mapper>
在mybatis.cfg.xml加载映射文件
- 在mybatis.cfg.xml文件中加载User.xml
<!-- 加载映射文件 -->
<mappers>
<mapper resource="first/User.xml"></mapper>
</mappers>
- 程序编写
package first;
import entity.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.Test;
import java.io.IOException;
import java.io.InputStream;
public class MybatisFirst {
//根据id查询用户信息,得到一条记录结果
@Test
public void findUserById() throws IOException {
//Mybatis配置文件
String resource = "mybatis.cfg.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建会话工厂,传入mybatis的配置文件信息
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂得到sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//通过sqlSession操作数据库
//第一个参数:映射文件中statement的id,等于namespace + "." + statement的id
//第二个参数:指定和映射文件中所匹配的parameterType类型的参数
//sqlSession.selectOne结果是与映射文件中所匹配的resultType类型的对象
User user = sqlSession.selectOne("test.findUserById", 1);
System.out.println(user);
//释放资源
sqlSession.close();
}
}
根据用户名称模糊查询用户信息
映射文件
- 使用User.xml,添加根据用户名称模糊查询用户信息的sql语句
<!-- 根据用户名称模糊查询用户信息
resultType:指定就是单挑记录所映射的java对象类型
${}:表示拼接sql串,将接收到参数的内容不加任何修饰拼接到sql中
使用${}拼接sql,引起sql注入
${value}:接收输入参数的内容,如果传入类型是简单类型,${}中只能使用value -->
<select id="findUserByName" parameterType="java.lang.String" resultType="entity.User">
SELECT * FROM user WHERE username LIKE "%"#{username}"%"
</select>
插入用户
<insert id="addUser" parameterType="entity.User">
INSERT INTO user(username, birthday, sex, address)
VALUE(#{username}, #{birthday}, #{sex}, #{address})
</insert>
- 自增主键返回
mysql自增主键,执行insert提交之前自动生成一个主键。
通过mysql函数获取到刚查询如记录的自增主键:
LAST_INSERT_ID()
INSERT之后调用次函数
<!-- 添加用户
parameterType:指定输入参数类型是User
运行最后提交事务 -->
<insert id="addUser" parameterType="entity.User">
<!--
将插入数据的主键返回,返回到user对象中
SELECT LAST_INSERT_ID():得到刚insert进去记录的主键值,只适用于自增主键
keyProperty:将查询到的主键值设置到parameterType指定的对象到哪个属性
order:SELECT LAST_INSERT_ID()执行顺序,相对于insert语句来说它的执行顺序 -->
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO user(username, birthday, sex, address)
VALUE(#{username}, #{birthday}, #{sex}, #{address})
</insert>
- 非自增主键返回(使用uuid())
使用mysql的uuid()函数生成主键,需要修改表中id字段类型为string,长度设置成35位 - 执行思路
先通过uuid()查询到主键,将主键输入到sql语句中
执行uuid()语句顺序相对于insert语句之前执行
<insert id="addUser" parameterType="entity.User">
<!-- 使用mysql的uuid()生成主键
执行过程
首先通过uuid()得到主键,将主键设置到uesr对象的id属性中
其次在insert执行时,从user对象中取出id属性值 -->
<selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String">
SELECT uuid()
</selectKey>
INSERT INTO user(id, username, birthday, sex, address)
VALUE(#{id}, #{username}, #{birthday}, #{sex}, #{address})
</insert>
总结
-
parameterType
在映射文件中通过parameterType指定输入参数的类型 -
resultType
在映射文件中通过resultType指定输出结果的类型 -
#{}和${}
#{}表示一个占位符号,#{}接收输入参数,类型可以是简单类型,pojo、hashmap
如果接收简单类型,#{}中可以写成value或其他名称
#{}接收pojo对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性…的方式获取表 示 一 个 拼 接 符 号 , 会 引 用 s q l 注 入 , 所 以 不 建 议 使 用 {}表示一个拼接符号,会引用sql注入,所以不建议使用 表示一个拼接符号,会引用sql注入,所以不建议使用{}
接 收 输 入 参 数 , 类 型 可 以 是 简 单 类 型 , p o j o 、 h a s h m a p 如 果 接 收 简 单 类 型 , {}接收输入参数,类型可以是简单类型,pojo、hashmap 如果接收简单类型, 接收输入参数,类型可以是简单类型,pojo、hashmap如果接收简单类型,{}中只能写成value
${}接收pojo对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性…的方式获取对象属性值。 -
selectOne和selectList
selectOne表示查询出一条记录进行映射。
selectList表示查询出一个列表(多条记录)进行映射。
mybatis和hibernate本质区别和应用场景
-
hibernate:是一个标准ORM框架(对象关系映射)入门门槛较高,不需要程序写sql,sql语句自动生成。
对sql语句进行优化、修改较为困难。
应用场景:适用于需求变化不多的中小型项目,比如:后台管理系统,erp、orm、oa… -
mybatis:专注是sql本身,需要程序员自己编写sql语句,sql修改、优化比较方便。mybatis是一个不完全的ORM框架,虽然程序员自己写sql,mybatis也可以实现映射(输入映射、输出映射)。
应用场景:适用于需求变化较多的项目,比如:互联网项目
企业进行技术选型,以低成本,高回报作为技术选型的原则,适用项目组的技术力量进行选择
mybatis开发dao的方法
SqlSession使用范围
SqlSessionFactoryBuilder
通过SqlSessionFactoryBuileder创建会话工厂SqlSessionFactory
将SqlSessionFactoryBuilder当成一个工具类使用即可,不需要使用单例管理
在需要创建SqlSessionFactory时候,只需要new一次SqlSessionFactoryBuilder即可
SqlSessionFactory
通过SqlSessionFactory创建SqlSession,使用单例模式管理sqlSessionFactory(工厂一旦创建,使用一个实例)
将来mybatis和spring整合后,使用单例模式管理sqlSessionFactory
SqlSession
SqlSession是一个面向用户的接口。
SqlSession中提供了很多操作数据库的方法:如selectOne、selectList…
SqlSession是线程不安全的,在SqlSession实现类中除了有接口中的方法(操作数据库的方法)还有数据域属性。
SqlSession最佳应用场合在方法体内,定义成局部变量使用。
总结原始dao开发问题
- dao接口实现类方法中存在大量模板方法,设想能否将这些代码提取出来,大大减轻程序员的工作量。
- 调用sqlSession方法时将statement的id硬编码了
- 调用sqlSession方法时传入的变量,由于sqlSession方法使用泛型,即使变量类型传入错误,在编译阶段也不报错,不利于开发。
mapper代理方法(程序员值需要mapper接口(相当于dao接口))
思路
-
编写maper.xml映射文件
-
编写mapper接口(相当于dao接口)mybatis可以自动生成mapper接口实现类代理对象
-
开发规范
- 在mapper.xml中namespace等于mapper接口地址
<!-- namespace命名空间,作用就是对sql进行分类化管理,理解sql隔离
注意:使用mapper代理方法开发,namespace有特殊重要的作用,namespace等于mapper接口地址
-->
<mapper namespace="dao.UserMapper">
- mapper.java接口中的方法名和mapper.xml中statemenet的id一致
- mapper.java接口中的方法输入参数类型和mapper.xml中statement的parameterType指定的类型一致
- mapper.java接口中的方法返回值类型和mapper.xml中statement的resultType指定的类型一致
<select id="findUserById" parameterType="java.lang.String" resultType="entity.User">
SELECT * FROM user WHERE id=#{id}
</select>
//根据用户id查询用户信息
public User findUserById(String id) throws Exception;
- 总结
以上开发规范主要是对下边的代码进行统一生成:
User user = sqlSession.selectOne(“test.findUserById”, id);
sqlSession.insert(“test.insertUser”, user);…
@Test
public void findUserById() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
//创建UserMapper对象,mybatis自动生成mapper代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用userMapper的方法
User user = userMapper.findUserById("1");
System.out.println(user);
}
代理对象内部调用selectOne或selectList
- 如果mapper方法返回单个pojo对象(非集合对象),代理对象内部通过selectOne查询数据库
- 如果mapper方法返回集合对象,代理对象内部通过selectList查询数据库。
mapper接口方法从参数只能有一个是否影响系统开发
- mapper接口方法参数只能有一个,系统是否不利于扩展维护?
系统框架中,dao层的代码是被业务层公用的。
即使mapper接口只有一个参数,可以使用包装类型的pojo满足不同的业务方法的需求。
注意:持久层方法的参数可以包装类型、map…,sevice方法中建议不要使用包装类型(不利于业务层的可扩展)
db.properties
<?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="mysql.properties"></properties>
<!-- 配置mybatis运行环境 -->
<!-- 和spring整合后environments配置将废除 -->
<environments default="cybatis">
<environment id="cybatis">
<!-- type="JDBC" 代表使用JDBC的提交想·和回滚来管理事务 -->
<transactionManager type="JDBC" />
<!-- mybatis提供了3种数据源类型,分别是:POOLED,UNPOOLED,JNDI -->
<!-- POOLED 表示支持JDBC数据源连接池 -->
<!-- UNPOOLED 表示不支持数据源连接池 -->
<!-- JNDI 表示支持外部数据源连接池 -->
<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>
</environment>
</environments>
<!-- 加载映射文件 -->
<mappers>
<mapper resource="first/User.xml"></mapper>
<mapper resource="sqlmap/UserMapper.xml"></mapper>
</mappers>
</configuration>
- 注意:MyBatis将按照下面的顺序来加载属性:
- 在properties元素体内定义的属性首先被读取
- 然后会读取properties元素中resource或url加载的属性,他会覆盖已读取的同名属性。
- 最后读取parameterType传递的属性,他会覆盖已读取的同名属性。
因此,通过parameterType传递的属性具有最高优先级,resource或url加载的属性次之,最低优先级的是properties元素体内定义的属性。
- 建议:
不要在properties元素体内添加任何属性,只将属性值定义在properties文件中。
在properties文件中定义属性名要有一定的特殊性,如:xxxx.xxxx.xxxx
settings全局参数配置
mybatis框架在运行时可以调整一些运行参数。
比如:开启二级缓存、开启延迟加载
全局参数将会影响mybatis的运行行为。
typeAliases(别名)重点
在mapper.xml中,定义了很多statement,statement需要parameterType指定输出结果的映射类型。
如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名定义,方便开发。
别名 | 映射的类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
自定义别名
- 单个别名定义
<!-- 别名定义 -->
<typeAliases>
<!-- 针对单个别名定义
type:类型的路径
alias:别名 -->
<typeAlias type="entity.User" alias="user"/>
</typeAliases>
- 批量定义别名(常用)
<typeAliases>
<!-- 针对单个别名定义
type:类型的路径
alias:别名 -->
<!--<typeAlias type="entity.User" alias="user"/>-->
<!-- 批量别名定义
指定包名,mybatis自动扫描包中的po类,自动定义别名,别名就是类名(首字母大写小写都可以) -->
<package name="entity"/>
</typeAliases>
typeHandlers(类型处理器)
mybatis中通过typeHandlers完成jdbc类型和java类型的转换。
通常情况下,mybatis提供的类型处理器满足日常需要,不需要进行自定义
映射配置
通过resource加载单个映射文件
<!-- 通过resource方法一次加载一个映射文件 -->
<mapper resource="sqlmap/UserMapper.xml"/>
通过mapper接口加载
<!-- 通过mapper接口加载映射文件
遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录
上面规范前提是:使用的是mapper代理方法 -->
<mapper class="sqlmap.UserMapper"/>
按照上边的规范,将mapper.java和mapper.xml放在一个目录,且同名
批量加载mapper
<!-- 批量加载mapper
指定mapper接口的包名,mybatis自动扫描包下边所有mapper接口进行加载
遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录
上边规范的前提是:使用mapper代理方法 -->
<package name="mapper"/>
输入映射
通过parameterType指定输入参数的类型,类型可以是简单类型、hashmap、pojo的包装类型
传递pojo的包装对象
需求
完成用户信息的综合查询,需要传入查询条件(可能包括用户信息、其他信息,比如商品、订单)
定义包装类型pojo
针对上边的需求,建议使用自定义的包装类型的pojo
在包装类型的pojo中将复杂的查询条件包装进去
//在这里包装所需要的查询条件
//用户查询条件
private UserCustom userCustom;
public UserCustom getUserCustom() {
return userCustom;
}
public void setUserCustom(UserCustom userCustom) {
this.userCustom = userCustom;
}
//可以包装其他的查询条件、订单、商品
mapper.xml
在UserMapper.xml中定义用户信息综合查询(查询条件复杂,通过高级查询进行复杂关联查询)
<!-- 用户信息综合查询
#{userCustom.sex}:取出pojo包装对象中性别值
#{userCustom.username}:取出pojo包装对象中用户名称 -->
<select id="findUserList" parameterType="UserQueryVo" resultType="UserCustom">
SELECT * FROM user where user.sex = #{userCustom.sex} and user.username like "%"#{userCustom.username}"%"
</select>
mapper.java
//用户信息综合查询
public List<UserCustom> findUserList(UserQueryVo userQueryVo) throws Exception;
@Test
public void testFindUserList() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//创建包装对象,设置查询条件
UserQueryVo userQueryVo = new UserQueryVo();
UserCustom userCustom = new UserCustom();
userCustom.setSex("男");
userCustom.setUsername("连");
userQueryVo.setUserCustom(userCustom);
List<UserCustom> list = userMapper.findUserList(userQueryVo);
System.out.println(list);
sqlSession.close();
}
输出映射
resultType
使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。
如果查询出来的列名和pojo中的属性名全部不一致,没有创建pojo对象。
只要查询出来的列名和pojo中的属性有一个一直,就会创建pojo对象。
<!-- 用户信息综合查询总数
parameterType:指定输入类型和findUserList一样
resultType:输出结果类型 -->
<select id="findUserCount" parameterType="UserQueryVo" resultType="int">
SELECT count(*) FROM user where user.sex = #{userCustom.sex} and user.username like "%"#{userCustom.username}"%"
</select>
小结
查询出来的结果集只有一行且一列,可以使用简单类型进行输出映射
输出pojo对象和pojo列表
不管是输出的pojo单个对象还是一个列表(list中包括pojo),在mapper.xml中resultType指定的类型是一样的
在mapper.java指定的方法返回值类型不一样:
- 输出单个pojo对象,方法返回值是单个对象类型
//根据用户id查询用户信息
public User findUserById(String id) throws Exception;
- 输出pojo对象list,方法返回值是 list
//用户信息综合查询
public List<UserCustom> findUserByName(String name) throws Exception;
生成的动态代理对象中是根据mapper方法的返回值类型确定是调用selectOne(返回单个对象调用)还是selectList(返回集合对象调用)
resultMap
mybatis中使用resultMap完成高级输出结果映射
需求
如果查询出来的列名和pojo的属性不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。
- 定义resultMap
- 使用resultMap作为statement的输出映射类型
将下边的sql使用UserCustom完成映射
SELECT id id_,username username_ FROM user WHERE id=#{id}
userCustom类中属性名和上边查询列名不一致
定义resultMap
<!-- 定义resultMap
将SELECT id id_,username username_ FROM user 和User类中的属性作一个映射关系
type:resultMap最终映射的java对象类型,可以使用别名
id:对resultMap的唯一标识 -->
<resultMap type="user" id="userResultMap">
<!-- id表示查询结果集中唯一标识
column:查询出来的列名
property:type所指定的pojo中的属性名
最终resultMap对column和property作一个映射关系(对应关系) -->
<id column="id_" property="id"/>
<!-- result:对普通列映射定义
column:查询出来的列名
property:type指定的pojo类型中的属性名
最终resultMap对column和property作一个映射关系(对应关系)-->
<result column="username_" property="username"/>
</resultMap>
使用resultMap作为statement的输出映射类型
<!-- 使用resultMap进行输出映射
resultMap:指定定义的resultMap的id,如果这个resultMap在其他的mapper文件,前边需要加namespace -->
<select id="findUserByIdResultMap" parameterType="java.lang.String" resultMap="userResultMap">
SELECT id id_,username username_ FROM user WHERE id=#{id}
</select>
小结
使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一直,该列才可以映射成功。
如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间做一个映射关系
动态sql
什么是动态sql
mybatis核心 对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接、组装。
需求
用户信息综合查询列表和用户信息查询列表总数这两个statement的定义使用动态sql
对查询条件进行判断,如果输入参数不为空才进行查询条件拼接
<!-- 用户信息综合查询
#{userCustom.sex}:取出pojo包装对象中性别值
#{userCustom.username}:取出pojo包装对象中用户名称 -->
<select id="findUserList" parameterType="UserQueryVo" resultType="UserCustom">
SELECT * FROM user
<!--
where可以自动去掉条件中的第一个and
-->
<where>
<if test="userCustom!=null">
<if test="userCustom.sex!=null and userCustom.sex!=''">
and user.sex = #{userCustom.sex}
</if>
<if test="userCustom.username!=null and userCustom.username!=''">
and user.username like "%"#{userCustom.username}"%"
</if>
</if>
</where>
sql片段
需求
将上边实现的动态sql判断代码块抽取出来,组成一个sql片段。其他的statement中就可以引用sql片段
定义sql片段
<!-- 定义sql片段
id:sql片段的唯一标示
经验:是基于单表来定义sql片段这样的话这个sql片段的可重用性才高
-->
<sql id="query_user_where">
<if test="userCustom!=null">
<if test="userCustom.sex!=null and userCustom.sex!=''">
AND USER.SEX = #{USERCUSTOM.SEX}
</if>
<if test="userCustom.username!=null and userCustom.username!=''">
AND USER.USERNAME LIKE "%"#{userCustom.username}"%"
</if>
</if>
</sql>
foreach
向sql传递数组或List,mybatis使用foreach解析
需求
在用户查询列表和查询总数的statement中早呢更加多个id的输入查询。
sql语句如下
两种方法:
select * from user where id=1 or id=2 or id=3;
select * from user where id in(1,2,3);
在输入参数类型中添加List
private List<String> ids;
修改mapper.xml
<if test="ids!=null">
<!-- 使用foreach遍历传入ids
collection:指定输入对象中集合属性
item:每次遍历生成的对象名
open:开始遍历时拼接串
close:结束遍历时拼接的串
separator:遍历的两个对象中需要拼接的串
-->
<!-- 使用实现下边的sql拼接
AND (id=1 OR id=2 OR id=3)
-->
<foreach collection="ids" item="user_id" open="AND (" close=")" separator="or">
<!-- 每个遍历需要拼接的串 -->
id=#{user_id}
</foreach>
</if>
resultMap
association
<mapper namespace="dao.OrdersMapperCustom">
<!-- 订单查询关联用户的resultMap -->
<resultMap type="entity.Orders" id="OrdersUserMap">
<!-- 配置映射的订单信息 -->
<!-- id:指定查询列中唯一标识,订单信息中的唯一标识,如果有多个列组成唯一标示,配置多个id
column:订单信息的唯一标识列
property:订单信息的唯一标识列所映射到Orders中哪个属性
-->
<id column="ordersId" property="ordersid"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 配置映射关联的用户信息 -->
<!-- association:用于映射关联查询单个对象的信息
property:要将关联查询的用户信息映射到Orders中哪个属性
-->
<association property="user" javaType="entity.User">
<!-- id:关联查询用户的唯一标识
column:指定唯一标识用户信息的列
javaType:映射到user的哪个属性
-->
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="address" property="address"/>
<result column="birthday" property="birthday"/>
<result column="sex" property="sex"/>
</association>
</resultMap>
<!-- 查询订单关联查询用户信息,使用resultMap -->
<select id="findOrdersUserMap" resultMap="OrdersUserMap">
select orders.*,user.username, user.sex, user.address from orders, user
where orders.user_id = user.id
</select>
collection
<resultMap type="entity.Orders" id="OrderAndOrderDetailResultMap" extends="OrdersUserMap">
<!-- 订单明细信息
一个订单关联查询出了多条明细,要使用collection进行应映射
collection:对关联查询到多条记录映射到集合对象中
property:将关联查询到多条记录映射到POJO中的哪个属性
ofType:指定映射到list集合属性中Pojo的类型
-->
<collection property="orderdetails" ofType="entity.Orderdetail">
<id column="orderdetail_id" property="id"/>
<id>
</collection>
</resultMap>
延迟加载
什么是延迟加载
resultMap可以实现高级映射,(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。
需求:
如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足需求,当我们需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。
延迟加载:先从单表查询、需要时再从关联表去关联查询,大大提高数据库性能,因为查询表单要比关联表查询多张表速度要快。
使用association实现延迟加载
需求
查询订单并且关联查询用户信息
需要定义两个mapper的方法对应的statement。
- 只查询订单信息
select * from orders;
在查询订单的statement中使用association去延迟加载(执行)下边的statement(关联查询用户信息)
<!-- 查询订单关联查询用户,用户信息需要延迟加载 -->
<select id="findOrdersUserLazyLoading" resultMap="OrdersUserLazyLoadingResultMap">
SELECT * FROM orders
</select>
- 关联查询用户信息
通过上边查询到的订单信息中user_id去关联查询用户信息
<!-- 根据id进行查询 -->
<select id="findUserById" parameterType="String" resultType="User">
SELECT * FROM user WHERE id=#{value}
</select>
上边先去执行findOrdersUserLazyLoading,当需要去查询用户的时候再去执行findUserById,通过resultMap的定义将延迟加载执行配置起来
延迟加载resultMap
使用association中的select指定延迟加载去执行的statement的id
<!-- 延迟加载的resultMap -->
<resultMap type="entity.Orders" id="OrdersUserLazyLoadingResultMap">
<!-- 对订单信息进行映射配置 -->
<id column="ordersId" property="ordersid"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 实现对用户信息进行延迟加载
select:指定延迟加载需要执行的statement的id(是根据user_id查询用户的statement)
要使用userMapper.xml中findUserById完成根据id(user_id)用户信息的查询
column:订单信息中关联用户信息查询的列,是user_id
关联查询的sql理解为:
SELECT orders.*,
(select username from user where orders.user_id = user.id)username,
(select sex from user where orders.user_id = `user`.id)sex
from orders;
-->
<association property="user" javaType="entity.User"
select="dao.UserMapper.findUserById" column=" user_id">
</association>
</resultMap>
测试
测试思路:
-
执行上边mapper方法(findOrdersUserLazyLoading),内部去调用dao.OrdersMapperCustom中的findOrdersUserLazyLoading值查询orders信息(单表)。
-
在程序中去遍历上一步查询出的List,当我们调用Orders中的getUser方法时,开始进行延迟加载
-
延迟加载,去调用UserMapper.xml中findUserById这个方法获取用户信息。
延迟加载配置
mybatis默认没有开启延迟加载,需要在SqlMapConfig.xml中setting配置。
<!-- 全局配置参数 -->
<settings>
<!-- 打开延迟加载的开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 将积极加载改为消极加载即按需要加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
查询缓存
什么是查询缓存
mybatis提供查询缓存,用于减轻数据压力,提高数据库性能
mybatis提供一级缓存,和二级缓存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RvHN53Kc-1574154614256)(https://s1.ax2x.com/2018/07/17/q6HFr.png)]
一级缓存是SqlSession级别的缓存
在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储数据。
不同的sqlSession之间的缓存区域(HashMap)是互相不影响的。
二级缓存是mapper级别的缓存
多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的
为什么要用缓存
如果缓存中有数据就不用从数据库中获取,大大提高系统性能。
一级缓存
一级缓存工作原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mRZiba39-1574154614257)(https://s1.ax2x.com/2018/07/17/q6TuY.png)]
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户id为1的用户,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
一级缓存应用
正式开发,是将mybatis和spring进行整合开发,事物控制在service中。
一个service方法中包括很多Mapper方法调用。
service{
//开始执行时,开启事务,创建SqlSession对象
//第一次调用mapper的方法findUserById()
//第二次调用mapper的方法findUserById(),从一级缓存中取数据
//方法结束,sqlSession关闭
}
如果是执行两次service调用查询相同的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空
二级缓存
原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cQxstUg3-1574154614258)(https://s1.ax2x.com/2018/07/17/q6YEl.png)]
首先开启mybatis的二级缓存。
sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。
sqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
二级缓存与一级缓存区别,二级缓存的范围更大,多个sqlSession可以共享一个UserMapper二级缓存。UserMapper有一个二级缓存区域(按namespace分),其他mapper也有自己的二级缓存区域。
每一个namespace的mapper有一个二级缓存区域
开启二级缓存
mybatis的二级缓存是mapper范围级别,除了再SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存。
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
在UserMapper.xml中开启二级缓存,UserMapper.xml下的sql执行完成会存储到他的缓存区域
<mapper namespace="dao.UserMapper">
<!-- 开启本mapper的namespace下的二级缓存 -->
<cache/>
调用pojo类实现序列化接口
public class User implements Serializable {
为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定在内存。
useCache配置
在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findUserCount" resultType="int" useCache="false">
SELECT COUNT(*) FROM USER
</select>
针对每次查询都需要最新的数据sql,要设置成useCachce=“false”,禁用二级缓存。
刷新缓存(就是清空缓存)
在mapper的同一个namespace中,如果有其他insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。
<insert id="addUser" parameterType="entity.User" flushCache="true">
总结:一般执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。
mybatis整合ehcache
ehcache是一个分布式缓存框架
分布缓存
我们系统为了提高系统并发,性能、一般对系统进行分布式部署(集群部署方式)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0YuhccE1-1574154614259)(https://s1.ax2x.com/2018/07/23/wr5uO.png)]
mybatis无法实现分布式缓存,需要和其他分布式缓存框架进行整合
整合方法
mybatis提供了一个cache接口,如果要实现自己的缓存逻辑,实现cache接口开发即可。
mybatis和ehcache整合,mybatis和ehcache整合包中提供了一个cache接口的实现类。
public interface Cache {
String getId();
void putObject(Object var1, Object var2);
Object getObject(Object var1);
Object removeObject(Object var1);
void clear();
int getSize();
ReadWriteLock getReadWriteLock();
}
mybatis默认实现的cache类是:PerpetualCache.java
加入ehcache包
ehcache-core-2.6.5.jar
mybatis-ehcache-1.0.2.jar
整合ehcache
配置mapper中cache中的type为ehcache对cache接口的实现类型
<mapper namespace="dao.UserMapper">
<!-- 开启本mapper的namespace下的二级缓存
type:指定cache接口的实现类的类型,mybaits默认使用PerpetualCache
要和ehcache整合,需要配置type为ehcache实现cache接口的类型
-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
二级缓存应用场景
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景:耗时较高的同级分析sql、电话账单查询sql等
实现方式如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置30分钟,60分钟等,根据需求而定
二级缓存局限性
mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求,对商品信息进行缓存,由于商品信息查询访问量过大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其他商品的信息,因为mybatis的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有的商品的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。
Spring和myBatis整合
整合思路
需要spring通过单例方式管理SqlSessionFactory
spring和mybatis整合生成代理对象,使用SqlSessionFactory创建SqlSession。(Spring和mybatis整合自动完成)
持久层的mapper、dao都需要由spring进行管理
整合环境
jar包:
mybatis3.2.7的jar包
spring3.2.0的jar包
mybatis和spring的整合包:早期ibatis和spring整合是由spring官方提供,mybatis和spring整合由mybatis提供。
sqlSessionFactory
在applicationContext.xml配置sqlSessionFactory
sqlSessionFactory在mybatis和spring的整合包下。
原始dao开发(和spring整合后)
User.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace命名空间,作用就是对sql进行分类化管理,理解sql隔离
注意:使用mapper代理方法开发,namespace有特殊重要的作用
-->
<mapper namespace="test">
<!-- 根据id进行查询 -->
<select id="findUserById" parameterType="String" resultType="User">
SELECT * FROM user WHERE id=#{value}
</select>
</mapper>
dao
public interface UserDao {
//根据Id进行查询
public User findUserById(String id) throws Exception;
}
dao接口实现类需要注入SqlSessionFactory,通过spring进行注入。
这里spring声明配置方式,配置dao的bean
让UserDaoImpl实现类继承SqlSessionDaoSupport
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao{
@Override
public User findUserById(String id) throws Exception {
//继承SqlSessionDaoSupport,通过this.getSqlSession()得到sqlSession
SqlSession sqlSession = this.getSqlSession();
User user = sqlSession.selectOne("test.findUserById", id);
return user;
}
}
整合
在applicationContext.xml中配置dao
<!-- 原始dao接口 -->
<bean id="userDao" class="ssm.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
测试程序
public class UserDaoTest {
private ApplicationContext applicationContext;
//在setUp这个方法中得到spring容器
@Before
public void setUp() throws Exception {
applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
}
@Test
public void findUserById() throws Exception {
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
//调用userDao的方法
User user = userDao.findUserById("1");
System.out.println(user);
}
}
mapper代理开发
mapper.xml和mapper.java
必须在同一个文件夹下
通过MapperFactoryBean创建代理对象
<!-- mapper配置
MapperFactoryBean:根据mapper接口生成代理对象
-->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!-- mapperInterface指定mapper接口 -->
<property name="mapperInterface" value="ssm.mapper.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>