Mybatis
Mybatis介绍
- MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,实质上Mybatis对ibatis进行一些改进。
- MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
- Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
Mybatis架构
-
mybatis配置
SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。
mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。 -
通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂
-
由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。
-
mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。
-
Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。
-
Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。
-
Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
Mybatis和hibernate不同
-
它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。
-
Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。
-
Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。 -
hibernate:是一个标准ORM框架(对象关系映射)。入门门槛较高的,不需要程序写sql,sql语句自动生成了。对sql语句进行优化、修改比较困难的。
应用场景:适用与需求变化不多的中小型项目,比如:后台管理系统,erp、orm、oa。。
-
mybatis:专注是sql本身,需要程序员自己编写sql语句,sql修改、优化比较方便。mybatis是一个不完全 的ORM框架,虽然程序员自己写sql,mybatis 也可以实现映射(输入映射、输出映射)。
应用场景:适用与需求变化较多的项目,比如:互联网项目。
- 企业进行技术选型,以低成本 高回报作为技术选型的原则,根据项目组的技术力量进行选择。
Mybatis入门程序
- 导入Mybatis的核心包、依赖包、数据库驱动包
- 创建pojo实体类
- 书写sql映射文件User.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">
<!-- namespace命名空间,作用就是对sql进行分类化管理,理解sql隔离
注意:使用mapper代理方法开发,namespace有特殊重要的作用
-->
<mapper namespace="test">
<!-- 在 映射文件中配置很多sql语句 -->
<!-- 需求:通过id查询用户表的记录 -->
<!-- 通过 select执行数据库查询
id:标识 映射文件中的 sql
将sql语句封装到mappedStatement对象中,所以将id称为statement的id
parameterType:指定输入 参数的类型,这里指定int型
#{}表示一个占位符号
#{id}:其中的id表示接收输入 的参数,参数名称就是id,如果输入 参数是简单类型,#{}中的参数名可以任意,可以value或其它名称
resultType:指定sql输出结果 的所映射的java对象类型,select指定resultType表示将单条记录映射成的java对象。
-->
<select id="findUserById" parameterType="int" resultType="cn.itcast.po.User">
SELECT * FROM USER WHERE id=#{value}
</select>
<!-- 根据用户名称模糊查询用户信息,可能返回多条
resultType:指定就是单条记录所映射的java对象 类型
${}:表示拼接sql串,将接收到参数的内容不加任何修饰拼接在sql中。
使用${}拼接sql,引起 sql注入
${value}:接收输入 参数的内容,如果传入类型是简单类型,${}中只能使用value
-->
<select id="findUserByName" parameterType="java.lang.String" resultType="cn.itcast.po.User">
SELECT * FROM USER WHERE username LIKE '%${value}%'
</select>
<!-- 添加用户
parameterType:指定输入 参数类型是pojo(包括 用户信息)
#{}中指定pojo的属性名,接收到pojo对象的属性值,mybatis通过OGNL获取对象的属性值
-->
<insert id="insertUser" parameterType="cn.itcast.po.User">
<!--
将插入数据的主键返回,返回到user对象中
SELECT LAST_INSERT_ID():得到刚insert进去记录的主键值,只适用与自增主键
keyProperty:将查询到主键值设置到parameterType指定的对象的哪个属性
order:SELECT LAST_INSERT_ID()执行顺序,相对于insert语句来说它的执行顺序
resultType:指定SELECT LAST_INSERT_ID()的结果类型
-->
<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})
<!--
使用mysql的uuid()生成主键
执行过程:
首先通过uuid()得到主键,将主键设置到user对象的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>
<!-- 删除 用户
根据id删除用户,需要输入 id值
-->
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id=#{id}
</delete>
<!-- 根据id更新用户
分析:
需要传入用户的id
需要传入用户的更新信息
parameterType指定user对象,包括 id和更新信息,注意:id必须存在
#{id}:从输入 user对象中获取id属性值
-->
<update id="updateUser" parameterType="cn.itcast.po.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address}
where id=#{id}
</update>
</mapper>
4.书写连接池配置文件db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=123456
5.书写mybatis主配置文件SqlMapConfig.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">
<!--properties中还可以配置一些属性名和属性值 -->
<!-- <property name="jdbc.driver" value=""/> -->
</properties>
<!-- 和spring整合后 environments配置将废除-->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理,事务控制由mybatis-->
<transactionManager type="JDBC" />
<!-- 数据库连接池,由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>
</environment>
</environments>
<!-- 加载 映射文件 -->
<mappers>
<mapper resource="sqlMap/User.xml"/>
</mappers>
</configuration>
6.引入log4j.properties配置文件(开发时,一定要将日志级别设置成debug)
# Global logging configuration
#\u5728\u5f00\u53d1\u73af\u5883\u4e0b\u65e5\u5fd7\u7ea7\u522b\u8981\u8bbe\u7f6e\u6210DEBUG\uff0c\u751f\u4ea7\u73af\u5883\u8bbe\u7f6e\u6210info\u6216error
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
7.测试程序
package cn.itcast.test;
import cn.itcast.po.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;
import java.util.Date;
import java.util.List;
public class MybatisFirst {
@Test
public void findUserById() throws IOException {
//mybatis主配置文件的路径
String resource="SqlMapConfig.xml";
//得到配置文件的输入流
InputStream inputStream=Resources.getResourceAsStream(resource);
//创建sqlSessionFactouy
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
//根据生会话工厂创建sqlSession
SqlSession sqlSession=sqlSessionFactory.openSession();
/**
* 通过sqlSession操作数据库
* 第一个参数:命名空间+"."+statement的id
* 第二个参数指定和配置文件中所匹配的parameterType类型的参数
*/
User user=sqlSession.selectOne("test.findUserById",1);
sqlSession.commit();
System.out.println(user);
sqlSession.close();
}
@Test
public void findUserByName() throws IOException {
//mybatis主配置文件的路径
String resource="SqlMapConfig.xml";
//得到配置文件的输入流
InputStream inputStream=Resources.getResourceAsStream(resource);
//创建sqlSessionFactouy
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
//根据生会话工厂创建sqlSession
SqlSession sqlSession=sqlSessionFactory.openSession();
/**
* 通过sqlSession操作数据库
* 第一个参数:命名空间+"."+statement的id
* 第二个参数指定和配置文件中所匹配的parameterType类型的参数
*/
List<User> list=sqlSession.selectList("test.findUserByName","小明");
sqlSession.commit();
System.out.println(list);
sqlSession.close();
}
@Test
public void insertUser() throws IOException {
String resource="SqlMapConfig.xml";
//得到配置文件输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
//根据输入流得到sqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//根据工厂得到sqlSession对象
SqlSession sqlSession= sqlSessionFactory.openSession();
User user=new User();
user.setAddress("北京");
user.setBirthday(new Date());
user.setSex("男");
user.setUsername("张三");
//执行插入记录的操作,并得到插入记录的id
System.out.println(sqlSession.insert("test.insertUser", user));
sqlSession.commit();
sqlSession.close();
}
@Test
public void deleteUser() throws IOException {
String resources="SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resources);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession=sqlSessionFactory.openSession();
sqlSession.delete("test.deleteUser",1);
sqlSession.commit();
sqlSession.close();
}
@Test
public void updateUser() throws IOException {
String resources="SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resources);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession=sqlSessionFactory.openSession();
User user = new User();
user.setId(2);
user.setUsername("李四");
user.setSex("女");
sqlSession.update("test.updateUser",user);
sqlSession.commit();
sqlSession.close();
}
}
#{}和${}
//#{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
//${}表示拼接sql串,通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。
parameterType和resultType
- parameterType:指定输入参数类型,mybatis通过ognl从输入对象中获取参数值拼接在sql中。
- resultType:指定输出结果类型,mybatis将sql查询结果的一行记录数据映射为resultType指定类型的对象。
SqlSession的使用范围
SqlSessionFactoryBuilder
- 通过SqlSessionFactoryBuilder创建会话工厂SqlSessionFactory,将SqlSessionFactoryBuilder当成一个工具类使用即可,不需要使用单例管理SqlSessionFactoryBuilder。在需要创建SqlSessionFactory时候,只需要new一次SqlSessionFactoryBuilder即可。
SqlSessionFactory
- 通过SqlSessionFactory创建SqlSession,使用单例模式管理sqlSessionFactory(工厂一旦创建,使用一个实例)。将来mybatis和spring整合后,使用单例模式管理sqlSessionFactory。
SqlSession
- SqlSession是一个面向用户(程序员)的接口。
SqlSession中提供了很多操作数据库的方法:如:selectOne(返回单个对象)、selectList(返回单个或多个对象。
SqlSession是线程不安全的,在SqlSesion实现类中除了有接口中的方法(操作数据库的方法)还有数据域属性。
SqlSession最佳应用场合在方法体内,定义成局部变量使用。
原始dao开发方法(程序员需要写dao接口和dao实现类)
- 思路
- 需要程序员自己写Dao接口和Dao实现类
- 向Dao实现类中注入SqlSessionFactory对象,通过SqlSessionFactory对象来创建SqlSession对象
- 总结原始 dao开发问题
-
dao接口实现类方法中存在大量模板方法,设想能否将这些代码提取出来,大大减轻程序员的工作量。
-
调用sqlsession方法时将statement的id硬编码了
-
调用sqlsession方法时传入的变量,由于sqlsession方法使用泛型,即使变量类型传入错误,在编译阶段也不报错,不利于程序员开发。
mapper代理方法(程序员只需要mapper接口(相当 于dao接口))
- 思路(mapper代理开发规范)
- 程序员还需要编写mapper.xml映射文件
- 程序员编写mapper接口需要遵循一些开发规范,mybatis可以自动生成mapper接口实现类代理对象。
- 开发规范:
- 在mapper.xml中namespace等于mapper接口地址
- mapper.java接口中的方法名和mapper.xml中statement的id一致
- mapper.java接口中的方法输入参数类型和mapper.xml中statement的parameterType指定的类型一致。
- mapper.java接口中的方法返回值类型和mapper.xml中statement的resultType指定的类型一致。
- mapper代理入门程序
在资源包下创建一个mapper文件夹,在此文件夹下面创建userMapper.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">
<!-- namespace命名空间,作用就是对sql进行分类化管理,理解sql隔离
注意:使用mapper代理方法开发,namespace有特殊重要的作用,
使用mapper代理开发namespace的值一定要等于Mapper接口的全类名
-->
<mapper namespace="cn.itcast.mapper.UserMapper">
<select id="findUserById" parameterType="int" resultType="cn.itcast.po.User">
SELECT * FROM USER WHERE id=#{value}
</select>
</mapper>
主配置文件sqlMapConfig.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">
<!--properties中还可以配置一些属性名和属性值 -->
<!-- <property name="jdbc.driver" value=""/> -->
</properties>
<!-- 和spring整合后 environments配置将废除-->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理,事务控制由mybatis-->
<transactionManager type="JDBC" />
<!-- 数据库连接池,由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>
</environment>
</environments>
<!-- 加载 映射文件 -->
<mappers>
<mapper resource="mapper/userMapper.xml"/>
</mappers>
</configuration>
Mapper接口
public interface UserMapper {
public User findUserById(int id) throws Exception;
}
-> log4j.properties和db.properties配置文件完全相同,就不做阐述
测试类MapperTest
public class MapperTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void setSqlSessionFactory() throws IOException {
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void fun() throws Exception {
SqlSession sqlSession=sqlSessionFactory.openSession();
//根据sqlSession得到UserMapper代理对象
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
//调用UserMapper对象中的方法
User user=userMapper.findUserById(1);
System.out.println(user);
}
}
#mapper接口方法参数只能有一个是否影响系统 开发
- mapper接口方法参数只能有一个,系统是否不利于扩展维护。
系统 框架中,dao层的代码是被业务层公用的。即使mapper接口只有一个参数,可以使用包装类型的pojo满足不同的业务方法的需求。
注意:持久层方法的参数可以包装类型、map。。。,service方法中建议不要使用包装类型(不利于业务层的可扩展)。
properties属性
- 需求:
将数据库连接参数单独配置在db.properties中,只需要在SqlMapConfig.xml中加载db.properties的属性值。
在SqlMapConfig.xml中就不需要对数据库连接参数硬编码。
将数据库连接参数只配置在db.properties中,原因:方便对参数进行统一管理,其它xml可以引用该db.properties。 - db.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root
sqlMapConfig.xml文件
<configuration>
<!-- 加载属性文件 -->
<properties resource="db.properties">
<!--properties中还可以配置一些属性名和属性值 -->
<!-- <property name="jdbc.driver" value=""/> -->
</properties>
<!-- 和spring整合后 environments配置将废除-->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理,事务控制由mybatis-->
<transactionManager type="JDBC" />
<!-- 数据库连接池,由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>
</environment>
</environments>
<!-- 加载 映射文件 -->
<mappers>
<mapper resource="sqlMap/User.xml"/>
<mapper resource="mapper/userMapper.xml"/>
</mappers>
</configuration>
- properties特性:
注意: MyBatis 将按照下面的顺序来加载属性:
在 properties 元素体内定义的属性首先被读取。
然后会读取properties 元素中resource或 url 加载的属性,它会覆盖已读取的同名属性。
最后读取parameterType传递的属性,它会覆盖已读取的同名属性。
建议:
不要在properties元素体内添加任何属性值,只将属性值定义在properties文件中。
在properties文件中定义属性名要有一定的特殊性,如:XXXXX.XXXXX.XXXX
settings全局参数配置
- mybatis框架在运行时可以调整一些运行参数。
比如:开启二级缓存、开启延迟加载。。
全局参数将会影响mybatis的运行行为。
详细参见“学习资料/mybatis-settings.xlsx”文件
typeAliases(别名)重点
需求
在mapper.xml中,定义很多的statement,statement需要parameterType指定输入参数的类型、需要resultType指定输出结果的映射类型。
如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名,在mapper.xml中通过别名定义,方便开发。
如下:
- 单个别名定义
<typeAliases>
<!--type:类路径
alias:别名
-->
<typeAlias type="cn.itcast.mybatis.po.User" alias="user"></typeAlias>
</typeAliases>
- 批量别名定义(在在sqlMapConfig.xml中加入如下代码,会自动为包下面的所有类定义别名,别名为类名称,首字母大小写都可以)
<package name="cn.itcast.po">
typeHandlers(类型处理器)
类型处理器用于java类型和jdbc类型映射,如下:
select * from user where id = #{id}mybatis自带的类型处理器基本上满足日常需求,不需要单独定义。
mybatis支持类型处理器:
mappers(映射配置)
- 加载单个映射文件
<mapper resource="映射文件路径"/>
- 通过mapper接口加载单个mapper(注意:mapper.java和mapper.xml必须放在同一个文件夹下且同名,前提:使用mapper代理)
<mapper class="cn.itcast.mapper.UserMapper"/>
- 批量加载
<!--
指定mapper接口的包名,mybatis自动扫描包下面的所有mapper接口进行加载
遵循规范:mapper.java和mapper.xml必须放在同一个文件夹下且同名
前提:使用mapper代理
-->
<package name="cn.itcast.mapper"/>
输入映射
+通过parameterType指定输入参数的类型,类型可以是简单类型、hashmap、pojo的包装类型
。
传递pojo的包装对象
-
需求
完成用户信息的综合查询,需要传入查询条件很复杂(可能包括用户信息、其它信息,比如商品、订单的) -
定义包装类型pojo
针对上边需求,建议使用自定义的包装类型的pojo。
在包装类型的pojo中将复杂的查询条件包装进去。public class UserCustomerVo { private UserCustomer userCustomer; public UserCustomer getUserCustomer() { return userCustomer; } public void setUserCustomer(UserCustomer userCustomer) { this.userCustomer = userCustomer; } }
-
mapper.xml
<select id="findUserList" parameterType="UserCustomerVo" resultType="UserCustomer"> SELECT * FROM USER WHERE user.sex=#{userCustomer.sex} AND user.username LIKE '%${userCustomer.username}%' </select>
-
mapper.java
public List<UserCustomer> findUserList(UserCustomerVo userCustomerVo) throws Exception;
-
测试方法
@Test public void fun1() throws Exception { SqlSession sqlSession=sqlSessionFactory.openSession(); //根据sqlSession得到UserMapper代理对象 UserMapper userMapper=sqlSession.getMapper(UserMapper.class); //调用UserMapper对象中的方法 UserCustomer userCustomer=new UserCustomer(); userCustomer.setSex("男"); userCustomer.setUsername("明"); UserCustomerVo userCustomerVo=new UserCustomerVo(); userCustomerVo.setUserCustomer(userCustomer); List<UserCustomer> list=userMapper.findUserList(userCustomerVo); System.out.println(list); }
输出映射
resultType
使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。
如果查询出来的列名和pojo中的属性名全部不一致,没有创建pojo对象。
只要查询出来的列名和pojo中的属性有一个一致,就会创建pojo对象。
- 输出简单类型
- 需求
用户信息的综合查询列表总数,通过查询总数和上边用户综合查询列表才可以实现分页。
-
mapper.xml
<select id="findUserCount" parameterType="UserCustomerVo" resultType="int"> SELECT COUNT(*) FROM USER WHERE user.sex=#{userCustomer.sex} AND user.username LIKE '%${userCustomer.username}%' </select>
-
mapper.java
public int findUserCount(UserCustomerVo userCustomerVo) throws Exception;
-
测试代码
@Test
public void fun2() throws Exception {
SqlSession sqlSession=sqlSessionFactory.openSession();
//根据sqlSession得到UserMapper代理对象
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
//调用UserMapper对象中的方法
UserCustomer userCustomer=new UserCustomer();
userCustomer.setSex("男");
userCustomer.setUsername("明");
UserCustomerVo userCustomerVo=new UserCustomerVo();
userCustomerVo.setUserCustomer(userCustomer);
int count=userMapper.findUserCount(userCustomerVo);
System.out.println(count);
}
- 小结:查询出来的结果集只有一行且一列,则可以使用简单类型进行输出映射
输出pojo对象和pojo列表
-
输出pojo对象和pojo列表
不管是输出的pojo单个对象还是一个列表(list中包括pojo),在mapper.xml中resultType指定的类型是一样的。
在mapper.java指定的方法返回值类型不一样:- 输出单个pojo对象,方法返回值是单个对象类型
- 输出pojo对象list,方法返回值是List
-
**结论:**生成的动态代理对象中是根据mapper方法的返回值类型确定是调用selectOne(返回单个对象调用)还是selectList (返回集合对象调用 ).
resultMap
- mybatis中使用resultMap完成高级输出结果映射。
- resultMap使用方法
如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。
-
定义resultMap
-
使用resultMap作为statement的输出映射类型
- 将下边的sql使用User完成映射
SELECT id id_,username username_ FROM USER WHERE id=#{value}
User类中属性名和上边查询列名不一致。- 定义reusltMap
<!-- 定义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的输出映射类型
<select id="findUserByIdResultMap" parameterType="int" resultMap="userResultMap"> SELECT id id_,username username_ FROM USER WHERE id=#{id}; </select>
- mapper.java
public User findUserByIdResultMap(int id) throws Exception;
- 测试
@Test public void fun3() throws Exception { SqlSession sqlSession=sqlSessionFactory.openSession(); //根据sqlSession得到UserMapper代理对象 UserMapper userMapper=sqlSession.getMapper(UserMapper.class); //调用UserMapper对象中的方法 User user=userMapper.findUserByIdResultMap(1); System.out.println(user); }
- 小结:使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。
如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。
动态sql
什么是动态sql
mybatis核心 对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接、组装。
if、where和foreach元素
- if:只有当if中的test属性值为真时才会进行sql拼接
- where:where元素会自动判断组合条件下拼装的sql语句,只有where元素内的条件成立时,才会在拼接sql中加入where关键字,否则不会添加;即使where之后有多余的and或者or关键字,where元素也会自动去除。
- foreach:ql传递数组或List,mybatis使用foreach解析
需求1(测试where和if)
用户信息综合查询列表和用户信息查询列表总数这两个statement的定义使用动态sql。
对查询条件进行判断,如果输入参数不为空才进行查询条件拼接。
-
mapper.xml
<select id="findUserList" parameterType="UserCustomerVo" resultType="UserCustomer"> SELECT * FROM USER <where> <if test="userCustomer!=null"> <if test="userCustomer.sex!=null and userCustomer.sex!=''"> AND user.sex=#{userCustomer.sex} </if> <if test="userCustomer.username!=null and userCustomer.username!=''"> AND user.username LIKE '%${userCustomer.username}%' </if> </if> </where> </select>
-
测试代码
@Test public void fun1() throws Exception { SqlSession sqlSession=sqlSessionFactory.openSession(); //根据sqlSession得到UserMapper代理对象 UserMapper userMapper=sqlSession.getMapper(UserMapper.class); //调用UserMapper对象中的方法 UserCustomer userCustomer=new UserCustomer(); //userCustomer.setSex("男"); //userCustomer.setUsername("明"); UserCustomerVo userCustomerVo=new UserCustomerVo(); userCustomerVo.setUserCustomer(userCustomer); List<UserCustomer> list=userMapper.findUserList(userCustomerVo); System.out.println(list); }
-
结果
- 将性别和用户名都不设值时,打印出来的sql语句为:SELECT * FROM USER
- 分析:因为sex和username都为空,所以经过判断,where以及where后面的条件都没有拼接到sql语句中
需求2
在用户查询列表和查询总数的statement中增加多个id输入查询。
sql语句如下:
两种方法:
SELECT * FROM USER WHERE id=1 OR id=10 OR id=16
SELECT * FROM USER WHERE id IN(1,10,16)
- 在输入参数类型中添加List ids传入多个id
private List<Integer> ids; public List<Integer> getIds() { return ids; } public void setIds(List<Integer> ids) { this.ids = ids; }
- 修改mapper.xml
WHERE id=1 OR id=10 OR id=16
在查询条件中,查询条件定义成一个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 test="ids!=null"> <!-- 使用 foreach遍历传入ids collection:指定输入 对象中集合属性 item:每次遍历生成对象 open:开始遍历时拼接的串 close:结束遍历时拼接的串 separator:遍历的两个对象中需要拼接的串 --> <!-- 使用实现下边的sql拼接: AND (id=1 OR id=10 OR id=16) --> <foreach collection="ids" item="user_id" open="AND (" close=")" separator="or"> <!-- 每个遍历需要拼接的串 --> id=#{user_id} </foreach> <!-- 实现 “ and id IN(1,10,16)”拼接 --> <!-- <foreach collection="ids" item="user_id" open="and id IN(" close=")" separator=","> 每个遍历需要拼接的串 #{user_id} </foreach> --> </if> </if> </sql>
sql片段
-
需求
将上边实现的动态sql判断代码块抽取出来,组成一个sql片段。其它的statement中就可以引用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>
-
使用sql片段
<select id="findUserList" parameterType="cn.itcast.mybatis.po.UserQueryVo" resultType="cn.itcast.mybatis.po.UserCustom"> SELECT * FROM USER <!-- where可以自动去掉条件中的第一个and --> <where> <!-- 引用sql片段 的id,如果refid指定的id不在本mapper文件中,需要前边加namespace --> <include refid="query_user_where"></include> <!-- 在这里还要引用其它的sql片段 --> </where> </select>
一对一查询
使用resultType映射
- 需求
查询订单信息,关联查询创建订单的用户信息
- sql语句
确定查询的主表:订单表
确定查询的关联表:用户表
关联查询使用内链接?还是外链接?
由于orders表中有一个外键(user_id),通过外键关联查询用户表只能查询出一条记录,可以使用内链接。SELECT orders.*, USER.username, USER.sex, USER.address FROM orders, USER WHERE orders.user_id = user.id
- 创建pojo
将上边sql查询的结果映射到pojo中,pojo中必须包括所有查询列名。
原始的Orders.java不能映射全部字段,需要新创建的pojo。
创建 一个pojo继承包括查询字段较多的po类
public class OrdersCustom extends Orders {
private String username;
private String sex;
private String address;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
- mapper.xml
<mapper namespace="cn.itcast.mapper.OrdersCustomMapper"> <select id="findOrdersCustomList" resultType="ordersCustom"> SELECT orders.*, USER.username, USER.sex, USER.address FROM orders, USER WHERE orders.user_id = user.id </select> </mapper>
- mapper.java
public List<OrdersCustom> findOrdersCustomList() throws Exception;
使用resultMap映射
- 使用resultMap将查询结果中的订单信息映射到Orders对象中,在orders类中添加User属性,将关联查询出来的用户信息映射到orders对象中的user属性中。
package cn.itcast.po; import java.util.Date; public class Orders { private Integer id; private Integer userId; private String number; private Date createtime; private String note; private User user; } //get和set方法省略
- mapper.xml
- 定义resultMap
2.3.4.1 定义resultMap <!-- 订单查询关联用户的resultMap 将整个查询的结果映射到cn.itcast.mybatis.po.Orders中 --> <resultMap type="cn.itcast.mybatis.po.Orders" id="OrdersUserResultMap"> <!-- 配置映射的订单信息 --> <!-- id:指定查询列中的唯 一标识,订单信息的中的唯 一标识,如果有多个列组成唯一标识,配置多个id column:订单信息的唯 一标识 列 property:订单信息的唯 一标识 列所映射到Orders中哪个属性 --> <id column="id" property="id"/> <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="cn.itcast.mybatis.po.User"> <!-- id:关联查询用户的唯 一标识 column:指定唯 一标识用户信息的列 javaType:映射到user的哪个属性 --> <id column="user_id" property="id"/><!--注意此处id中的column属性值是orders表中与user对应的外键列名--> <result column="username" property="username"/> <result column="sex" property="sex"/> <result column="address" property="address"/> </association> </resultMap>
- statement定义
<!--使用resultMap接收映射结果集--> <select id="findOrdersResultMap" resultMap="OrdersUserResultMap"> SELECT orders.*, USER.username, USER.sex, USER.address FROM orders, USER WHERE orders.user_id = user.id </select>
- mapper.java
public List<OrdersCustom> findOrdersResultMap()throws Exception;
- 测试
@Test public void fun4() throws Exception { SqlSession sqlSession=sqlSessionFactory.openSession(); //根据sqlSession得到UserMapper代理对象 OrdersCustomMapper ordersCustomMapper=sqlSession.getMapper(OrdersCustomMapper.class); //调用UserMapper对象中的方法 List<OrdersCustom> ordersCustomList = ordersCustomMapper.findOrdersResultMap(); System.out.println(ordersCustomList); }
resultType和resultMap实现一对一查询小结
- 实现一对一查询:
- resultType:使用resultType实现较为简单,如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。
- 如果没有查询结果的特殊要求建议使用resultType。
- resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射pojo的属性中。
- resultMap可以实现延迟加载,resultType无法实现延迟加载。
一对多查询
- 需求
查询订单及订单明细的信息。 - sql语句
- 确定主查询表:订单表
- 确定关联查询表:订单明细表
- 在一对一查询基础上添加订单明细表关联即可。
SELECT
orders.*,
USER.username,
USER.sex,
USER.address,
orderdetail.id orderdetail_id,
orderdetail.items_id,
orderdetail.items_num,
orderdetail.orders_id
FROM
orders,
USER,
orderdetail
WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id
- 分析
-
使用resultType将上边的 查询结果映射到pojo中,订单信息的就是重复。
-
要求:
对orders映射不能出现重复记录。
在orders.java类中添加List orderDetails属性。
最终会将订单信息映射到orders中,订单所对应的订单明细映射到orders中的orderDetails属性中。 -
映射成的orders记录数为两条(orders信息不重复)
每个orders中的orderDetails属性存储了该 订单所对应的订单明细。
-
在orders中添加list订单明细属性
public class Orders { private Integer id; private Integer userId; private String number; private Date createtime; private String note; private User user; private List<Orderdetail> orderdetails; //get和set方法
-
mapper.xml
<select id="findOrderAndDetails" resultMap="findOrdersUserDetailsResultMap"> SELECT orders.*, USER.username, USER.sex, USER.address, orderdetail.id orderdetail_id, orderdetail.items_id, orderdetail.items_num, orderdetail.orders_id FROM orders, USER, orderdetail WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id </select>
-
resultMap定义
<!-- 订单及订单明细的resultMap 使用extends继承,不用在中配置订单信息和用户信息的映射 --> <resultMap type="cn.itcast.mybatis.po.Orders" id="OrdersAndOrderDetailResultMap" extends="OrdersUserResultMap"> <!-- 订单信息 --> <!-- 用户信息 --> <!-- 使用extends继承,不用在中配置订单信息和用户信息的映射 --> <!-- 订单明细信息 一个订单关联查询出了多条明细,要使用collection进行映射 collection:对关联查询到多条记录映射到集合对象中 property:将关联查询到多条记录映射到cn.itcast.mybatis.po.Orders哪个属性 ofType:指定映射到list集合属性中pojo的类型 --> <collection property="orderdetails" ofType="cn.itcast.mybatis.po.Orderdetail"> <!-- id:订单明细唯 一标识 property:要将订单明细的唯 一标识 映射到cn.itcast.mybatis.po.Orderdetail的哪个属性 --> <id column="orderdetail_id" property="id"/> <result column="items_id" property="itemsId"/> <result column="items_num" property="itemsNum"/> <result column="orders_id" property="ordersId"/> </collection> </resultMap>
-
mapper.java
public List<Orders> findOrderAndDetails()throws Exception;
-
测试
@Test public void fun5() throws Exception { SqlSession sqlSession=sqlSessionFactory.openSession(); //根据sqlSession得到UserMapper代理对象 OrdersCustomMapper ordersCustomMapper=sqlSession.getMapper(OrdersCustomMapper.class); //调用UserMapper对象中的方法 List<Orders> ordersCustomList = ordersCustomMapper.findOrderAndDetails(); System.out.println(ordersCustomList); }
-
小结:
- mybatis使用resultMap的collection对关联查询的多条记录映射到一个list集合属性中,会自动清除重复记录。
- 使用resultType实现:
将订单明细映射到orders中的orderdetails中,需要自己处理,使用双重循环遍历,去掉重复记录,将订单明细放在orderdetails中。
多对多查询
-
需求
查询用户及用户购买商品信息。 -
sql语句
查询主表是:用户表 关联表:由于用户和商品没有直接关联,通过订单和订单明细进行关联,所以关联表: orders、orderdetail、items SELECT orders.*, USER.username, USER.sex, USER.address, orderdetail.id orderdetail_id, orderdetail.items_id, orderdetail.items_num, orderdetail.orders_id, items.name items_name, items.detail items_detail, items.price items_price FROM orders, USER, orderdetail, items WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id AND orderdetail.items_id = items.id
-
映射思路
将用户信息映射到user中。
在user类中添加订单列表属性List orderslist,将用户创建的订单映射到orderslist
在Orders中添加订单明细列表属性Listorderdetials,将订单的明细映射到orderdetials
在OrderDetail中添加Items属性,将订单明细所对应的商品映射到Items -
mapper.xml
<!-- 查询用户及购买的商品信息,使用resultmap --> <select id="findUserAndItemsResultMap" resultMap="UserAndItemsResultMap"> SELECT orders.*, USER.username, USER.sex, USER.address, orderdetail.id orderdetail_id, orderdetail.items_id, orderdetail.items_num, orderdetail.orders_id, items.name items_name, items.detail items_detail, items.price items_price FROM orders, USER, orderdetail, items WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id AND orderdetail.items_id = items.id </select>
-
resultMap定义
<!-- 查询用户及购买的商品 --> <resultMap type="cn.itcast.mybatis.po.User" id="UserAndItemsResultMap"> <!-- 用户信息 --> <id column="user_id" property="id"/> <result column="username" property="username"/> <result column="sex" property="sex"/> <result column="address" property="address"/> <!-- 订单信息 一个用户对应多个订单,使用collection映射 --> <collection property="ordersList" ofType="cn.itcast.mybatis.po.Orders"> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <result column="createtime" property="createtime"/> <result column="note" property="note"/> <!-- 订单明细 一个订单包括 多个明细 --> <collection property="orderdetails" ofType="cn.itcast.mybatis.po.Orderdetail"> <id column="orderdetail_id" property="id"/> <result column="items_id" property="itemsId"/> <result column="items_num" property="itemsNum"/> <result column="orders_id" property="ordersId"/> <!-- 商品信息 一个订单明细对应一个商品 --> <association property="items" javaType="cn.itcast.mybatis.po.Items"> <id column="items_id" property="id"/> <result column="items_name" property="name"/> <result column="items_detail" property="detail"/> <result column="items_price" property="price"/> </association> </collection> </collection> </resultMap>
-
mapper.java
public List<User> findUserAndItemsResultMap()throws Exception;
-
测试
@Test public void fun6() throws Exception { SqlSession sqlSession=sqlSessionFactory.openSession(); //根据sqlSession得到UserMapper代理对象 OrdersCustomMapper ordersCustomMapper=sqlSession.getMapper(OrdersCustomMapper.class); //调用UserMapper对象中的方法 List<User> userList = ordersCustomMapper.findUserAndItemsResultMap(); System.out.println(userList); }
多对多查询总结
- 将查询用户购买的商品信息明细清单,(用户名、用户地址、购买商品名称、购买商品时间、购买商品数量)
针对上边的需求就使用resultType将查询到的记录映射到一个扩展的pojo中,很简单实现明细清单的功能。 - 一对多是多对多的特例,如下需求:
查询用户购买的商品信息,用户和商品的关系是多对多关系。 - 需求1:
查询字段:用户账号、用户名称、用户性别、商品名称、商品价格(最常见)
企业开发中常见明细列表,用户购买商品明细列表,
使用resultType将上边查询列映射到pojo输出。 - 需求2:
查询字段:用户账号、用户名称、购买商品数量、商品明细(鼠标移上显示明细)
使用resultMap将用户购买的商品明细列表映射到user对象中。 - 总结:
使用resultMap是针对那些对查询结果映射有特殊要求的功能,比如特殊要求映射成list中包括 多个list。
5 resultMap总结
-
resultType:
-
作用:
将查询结果按照sql列名pojo属性名一致性映射到pojo中。
-
场合:
常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到pojo中,在前端页面遍历list(list中是pojo)即可。
-
resultMap:
使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。 -
association:
作用:将关联查询信息映射到一个pojo对象中。
场合:
为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的pojo属性中,比如:查询订单及关联用户信息。 -
使用resultType无法将查询结果映射到pojo对象的pojo属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap。
-
collection:
-
作用:
将关联查询信息映射到一个list集合中。
-
场合:
为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样做的目的也是方便对查询结果集进行遍历查询。
如果使用resultType无法将查询结果映射到list集合中。
延迟加载
什么是延迟加载
- resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。
- 需求:
如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求,当我们需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。 - 延迟加载:先从单表查询、需要时再从关联表去关联查询,大大提高 数据库性能,因为查询单表要比关联查询多张表速度要快。
使用association实现延迟加载
- 需求
查询订单并且关联查询用户信息 - mapper.xml
需要定义两个mapper的方法对应的statement。
-
只查询订单信息
SELECT * FROM orders -
在查询订单的statement中使用association去延迟加载(执行)下边的satatement(关联查询用户信息)
<!-- 查询订单关联查询用户,用户信息需要延迟加载 --> <select id="findOrdersUserLazyLoading" resultMap="OrdersUserLazyLoadingResultMap"> SELECT * FROM orders </select>
-
关联查询用户信息
- 通过上边查询到的订单信息中user_id去关联查询用户信息
使用UserMapper.xml中的findUserById
<select id="findUserById" parameterType="int" resultType="cn.itcast.po.User"> SELECT * FROM USER WHERE id=#{value} </select>
- 通过上边查询到的订单信息中user_id去关联查询用户信息
- 上边先去执行findOrdersUserLazyLoading,当需要去查询用户的时候再去执行findUserById,通过resultMap的定义将延迟加载执行配置起来。
-
延迟加载resultMap
使用association中的select指定延迟加载去执行的statement的id。<!-- 延迟加载的resultMap --> <resultMap type="cn.itcast.mybatis.po.Orders" id="OrdersUserLazyLoadingResultMap"> <!--对订单信息进行映射配置 --> <id column="id" property="id"/> <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)用户信息的查询,如果findUserById不在本mapper中需要前边加namespace 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="cn.itcast.mybatis.po.User" select="cn.itcast.mybatis.mapper.UserMapper.findUserById" column="user_id"> <!-- 实现对用户信息进行延迟加载 --> </association> </resultMap>
-
在sqlMapConfig.xml中开启延迟加载
<!-- 全局配置参数,需要时再设置 --> <settings> <!-- 打开延迟加载 的开关 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 将积极加载改为消极加载即按需要加载 --> <setting name="aggressiveLazyLoading" value="false"/> <!-- 开启二级缓存 --> <setting name="cacheEnabled" value="true"/> </settings>
-
mapper.java
public List<Orders> findOrderUserLazyLoding()throws Exception;
-
测试
@Test public void fun7() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); //根据sqlSession得到UserMapper代理对象 OrdersCustomMapper ordersCustomMapper = sqlSession.getMapper(OrdersCustomMapper.class); //调用UserMapper对象中的方法 List<Orders> ordersList = ordersCustomMapper.findOrderUserLazyLoding(); for (Orders orders : ordersList) { System.out.println(orders.getUser()); } }
延迟加载思考
- 不使用mybatis提供的association及collection中的延迟加载功能,如何实现延迟加载??
实现方法如下:
定义两个mapper方法:
1、查询订单列表
2、根据用户id查询用户信息
实现思路:
先去查询第一个mapper方法,获取订单信息列表
在程序中(service),按需去调用第二个mapper方法去查询用户信息。
总之:
使用延迟加载方法,先去查询简单的sql(最好单表,也可以关联查询),再去按需要加载关联查询的其它信息。
查询缓存
- 什么是查询缓存
mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。
mybaits提供一级缓存,和二级缓存。 - 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
- 二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
- 为什么要用缓存?
如果缓存中有数据就不用从数据库中获取,大大提高系统性能。
一级缓存
级缓存工作原理
- 第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
- 如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
- 第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
一级缓存应用
- 正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
一个service方法中包括 很多mapper方法调用。
service{
//开始执行时,开启事务,创建SqlSession对象
//第一次调用mapper的方法findUserById(1)
//第二次调用mapper的方法findUserById(1),从一级缓存中取数据
//方法结束,sqlSession关闭
}
如果是执行两次service调用查询相同 的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空。
二级缓存
原理
- 首先开启mybatis的二级缓存。
- sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。
- 如果SqlSession3去执行相同 mapper下sql,执行commit提交,清空该 mapper下的二级缓存区域的数据。
- sqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
- 二级缓存与一级缓存区别,二级缓存的范围更大,多个sqlSession可以共享一个UserMapper的二级缓存区域。
UserMapper有一个二级缓存区域(按namespace分),其它mapper也有自己的二级缓存区域(按namespace分)。
每一个namespace的mapper都有一个二缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同 的二级缓存区域中。
开启二级缓存
- mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存。
在核心配置文件SqlMapConfig.xml中加入<setting name="cacheEnabled" value="true"/> </settings>
- 在UserMapper.xml中开启二缓存,UserMapper.xml下的sql执行完成会存储到它的缓存区域(HashMap)。
开启本mapper的namespace下的二级缓存 <cache/>
- 调用pojo类实现序列化接口
public class User implements Serializable{}
为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一样在内存。 - 测试方法
// 二级缓存测试 @Test public void testCache2() throws Exception { SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); SqlSession sqlSession3 = sqlSessionFactory.openSession(); // 创建代理对象 UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class); // 第一次发起请求,查询id为1的用户 User user1 = userMapper1.findUserById(1); System.out.println(user1); //这里执行关闭操作,将sqlsession中的数据写到二级缓存区域 sqlSession1.close(); //使用sqlSession3执行commit()操作 UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class); User user = userMapper3.findUserById(1); user.setUsername("张明明"); userMapper3.updateUser(user); //执行提交,清空UserMapper下边的二级缓存 sqlSession3.commit(); sqlSession3.close(); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class); // 第二次发起请求,查询id为1的用户 User user2 = userMapper2.findUserById(1); System.out.println(user2); sqlSession2.close(); }
- useCache配置
在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
总结:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。 - 刷新缓存(就是清空缓存)
在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。
设置statement配置中的flushCache=“true” 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
如下:
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">
总结:一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。
mybatis整合ehcache
ehcache是一个分布式缓存框架。
分布缓存
- 我们系统为了提高系统并发,性能、一般对系统进行分布式部署(集群部署方式)
- 不使用分布缓存,缓存的数据在各各服务单独存储,不方便系统开发。所以要使用分布式缓存对缓存数据进行集中管理。
- mybatis无法实现分布式缓存,需要和其它分布式缓存框架进行整合。
整合方法(掌握)
mybatis提供了一个cache接口,如果要实现自己的缓存逻辑,实现cache接口开发即可。
mybatis和ehcache整合,mybatis和ehcache整合包中提供了一个cache接口的实现类。
- 加入ehcache包
ehcache-core-2.6.5.jar和mybatis-ehcache-1.0.2.jar - 整合ehcache
配置mapper中cache中的type为ehcache对cache接口的实现类型。
<!-- 开启本mapper的namespace下的二缓存
type:指定cache接口的实现类的类型,mybatis默认使用PerpetualCache
要和ehcache整合,需要配置type为ehcache实现cache接口的类型
-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
- 加入ehcache的配置文件
在classpath下配置ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="F:\develop\ehcache" />
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
二级缓存应用场景
- 二级应用场景
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。 - 实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。
二级缓存局限性
- mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。