1 Mybatis的介绍
Mybatis就是一个封装jdbc的持久层框架,它和hibernate都属于ORM框架,但是具体的说,hibernate是一个完全的orm框架,而mbatis是一个不完全的orm框架.
Mybatis让程序员只关注sql本身,而不需要去关注如连接的创建、statement 的创建等操作。
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对象并返回。
2 分析jdbc的问题
2.1 原生态的jdbc代码
publicstaticvoid main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1、加载数据库驱动
Class.forName("oracle.jdbc.OracleDriver");
//2、通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:XE", "xiaoming", "root");
//3、定义sql语句 ?表示占位符
String sql = "select * from t_user where t_name = ?";
//4、获取预处理statement
preparedStatement = connection.prepareStatement(sql);
//5、设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1, "程冠西");
//6、向数据库发出sql执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
//7、遍历查询结果集
while(resultSet.next()){
System.out.println(resultSet.getString("t_id")+""+resultSet.getString("t_name"));
}
} catch (Exception e) {
e.printStackTrace();
}finally{
//8、释放资源
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
2.2 问题总结
1、在创建连接时,存在硬编码
配置文件
2、在执行statement时存在硬编码
配置文件(映射文件)
3、频繁的开启的关闭数据库连接,会造成数据库性能下降。
数据库连接池(全局配置文件)
3 Mybatis的框架原理
4 入门程序
4.1 需求
对订单商品案例中的用户表进行增删改查操作
1、 根据用户id查询用户信息
2、 根据用户名称模糊查询用户列表
3、 添加用户
4、 删除用户(练习)
5、 修改用户(练习)
4.2 环境准备
l Jdk:1.7
l Ide: MyEclipse 10
l Mybatis:3.2.7
l 数据库:mysql
4.3 下载mybaits
Mybatis的代码同github.com管理,下载地址: https://github.com/mybatis/mybatis-3/releases
4.4 数据库脚本初始化
4.5 工程搭建
Mybatis的核心包和依赖包(必须)
mysql的驱动包(必须)
Junit(非必须)
4.6 代码实现
4.6.1 创建po类
4.6.2 创建全局配置文件
在config目录下,创建SqlMapConfig.xml,该名称不是固定不变的
db.driver=oracle.jdbc.OracleDriver
db.url=jdbc:oracle:thin:@localhost:1521:XE
4.6.3 需求开发
4.6.3.1 根据用户id查询用户信息
4.6.3.1.1 映射文件
4.6.3.1.2 在全局配置文件夹中加载映射文件
4.6.3.1.3 测试代码
4.6.4 根据用户名称模糊查询用户列表
4.6.4.1.1 映射文件
concat("%",#{value},"%")
concat(concat('%',#{value}),'%')
4.6.4.1.2 在全局配置文件夹中加载映射文件
4.6.4.1.3 测试代码
4.6.5 添加用户
4.6.5.1.1 映射文件
4.6.5.1.2 在全局配置文件夹中加载映射文件
4.6.5.1.3 测试代码
4.6.5.1.4 主键返加自增长序列
Mysql
如果使用UUID 字段就必需设置为字符串
select sys_guid()from dual;
oracle
4.6.5.1.5 selectKey语句属性配置细节:
属性 |
描述 |
取值 |
keyProperty |
selectKey 语句生成结果需要设置的属性。 |
|
resultType |
生成结果类型,MyBatis 允许使用基本的数据类型,包括String 、int类型。 |
|
order |
1:BEFORE,会先选择主键,然后设置keyProperty,再执行insert语句; 2:AFTER,就先运行insert 语句再运行selectKey 语句。 |
BEFORE AFTER |
4.6.6 小结
4.6.6.1 parameterType和resultType
parameterType指定输入参数的java类型,可以填写别名或java类的全限定名.
4.6.6.2 #{}和${}
#{}:相当于预处理中的占位符?。
#{}里面的参数表示接收java输入参数的名称。
#{}可以接受HashMap、简单类型、POJO类型的参数。
当接受简单类型的参数时,#{}里面可以是value,也可以是其他。
#{}可以防止SQL注入。
${}:相当于拼接SQL串,对传入的值不做任何解释的原样输出。
${}会引起SQL注入,所以要谨慎使用。
${}可以接受HashMap、简单类型、POJO类型的参数。
当接受简单类型的参数时,${}里面只能是value。
4.6.6.3 selecxtOne和selectList
selecxtOne:只能查询0或1条记录,大于1条记录的话,则报错
selectList:可以查询0或N条记录
5 mybatis开发dao
mybatis在项目中主要使用的地方就是开发dao(数据访问层 ),所以下面讲解一下一步mybatis开发dao的方法。有两种方式
1,原始dao开发方式(ibatis遗留下来的)
2.Mapper代理发发方式(推荐)
5.1 需求
1、 根据用户id来查询用户信息
2、 根据用户名称来模糊查询用户信息列表
3、 添加用户
5.2 原始dao开发方式
5.2.1 思路
程序员需要写dao和dao实现类
5.2.2 编程步骤
1、 根据需求创建po类
2、 编写全局配置文件
3、 根据需求编写映射文件
4、 加载映射文件
5、 编写dao
6、 编写dao实现类
7、 编写测试代码
5.2.3 程序编写
步骤中的1、2、3、4都在入门程序中进行了编写,此处不需要重新编写。
5.2.3.1 开发dao接口
publicinterface UserDao {
//根据用户ID来查询用户信息
public User findUserById(int id);
//根据用户名称来模糊查询用户信息列表
public List<User> findUsersByName(String username);
//添加用户
publicvoid insertUser(User user);
}
5.2.3.2 开发dao实现类
5.2.3.2.1 SqlSession使用范围
通过入门程序,大家可以看出,在测试代码中,有大量的重复代码。所以我们第一反应就是想给它抽取出共性的部分,但是SqlSession、SqlSessionFactory、SqlSessionFactoryBuilder有着各自的生命周期,因为这些生命周期的不同,抽取时要有针对性的处理。
所以在抽取之前,我们先来了解并总结下它们三个的生命周期。
1、 SqlSessionFactoryBuilder
它的作用只是通过配置文件创建SqlSessionFactory,所以只要创建出SqlSessionFactory,它就可以销毁了。所以说,它的生命周期是在方法之内。
2、 SqlSessionFactory
它的作用是创建SqlSession的工厂,工厂一旦创建,除非应用停掉,不要销毁。
所以说它的生命周期是在应用范围内。这里可以通过单例模式来管理它。
在mybatis整合spring之后,最好的处理方式是把SqlSessionFactory交由spring来做单例管理。
3、 SqlSession
SqlSession是一个面向用户(程序员)的接口,它的默认实现是DefaultSqlSession。
Mybatis是通过SqlSession来操作数据库的。SqlSession中不仅包含要处理的SQL信息,还包括一些数据信息,所以说它是线程不安全的,因此它最佳的生命周期范围是在方法体之内。
5.2.3.2.2 Dao实现类代码
需要向dao实现类中注入SqlSessionFactory,在方法体内通过SqlSessionFactory创建SqlSession
要注意SqlSession和SqlSessionFactory的生命周期。
publicclass UserDaoImpl implements UserDao {
//注入SqlSessionFactory
private SqlSessionFactory sqlSessionFactory;
//使用构造方法来初始化SqlSessionFactory
public UserDaoImpl(SqlSessionFactory sqlSessionFactory){
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public User findUserById(int id) {
//通过工厂,在方法内部获取SqlSession,这样就可以避免线程不安全
SqlSession sqlSession = sqlSessionFactory.openSession();
//返回结果集
return sqlSession.selectOne("test.findUserById", id);
}
@Override
public List<User> findUsersByName(String username) {
//通过工厂,在方法内部获取SqlSession,这样就可以避免线程不安全
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession.selectList("test.findUsersByName", username);
}
@Override
publicvoid insertUser(User user) {
//通过工厂,在方法内部获取SqlSession,这样就可以避免线程不安全
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.insert("test.insertUser", user);
}
}
5.2.3.3 编写测试代码
5.2.4 问题总结
原始dao开发存在一些问题:
- 存在一定量的模板代码。比如:通过SqlSessionFactory创建SqlSession;调用SqlSession的方法操作数据库;关闭Sqlsession。
- 存在一些硬编码。调用SqlSession的方法操作数据库时,需要指定statement的id,这里存在了硬编码。
5.3 Mapper代理开发方式(推荐)
Mapper代理的开发方式,程序员只需要编写mapper接口(相当于dao接口)即可。Mybatis会自动的为mapper接口生成动态代理实现类。
不过要实现mapper代理的开发方式,需要遵循一些开发规范。
5.3.1 开发规范
1、 mapper接口的全限定名要和mapper映射文件的namespace的值相同。
2、 mapper接口的方法名称要和mapper映射文件中的statement的id相同;
3、 mapper接口的方法参数只能有一个,且类型要和mapper映射文件中statement的parameterType的值保持一致。
4、 mapper接口的方法参数如输入多个时接口中必需加入注解@Param,且mapper映射文件中不需要parameterType
5、 mapper接口的返回值类型要和mapper映射文件中statement的resultType值或resultMap中的type值保持一致;
通过规范式的开发mapper接口,可以解决原始dao开发当中存在的问题:
1、 模板代码已经去掉;
2、 剩下去不掉的操作数据库的代码,其实就是一行代码。这行代码中硬编码的部分,通过第一和第二个规范就可以解决。
5.3.2 编程步骤
1、 根据需求创建po类
2、 编写全局配置文件
3、 根据需求编写映射文件
4、 加载映射文件
5、 编写mapper接口
6、 编写测试代码
5.3.3 程序编写
步骤中的1、2都在入门程序中进行了编写,此处不需要重新编写。
5.3.3.1 编写mapper映射文件
重新定义mapper映射文件UserMapper.xml(内容同Users.xml,除了namespace的值),放到新创建的目录mapper下。
5.3.3.2 加载mapper映射文件
<!-- 加载mapper -->
<mappers>
<mapperresource="mapper/UserMapper.xml"/>
</mappers>
5.3.3.3 编写mapper接口
内容同UserDao接口一样:
5.3.3.4 编写测试代码
// 声明全局的SqlSessionFactory privatestatic SqlSessionFactory sqlSessionFactory ; @BeforeClass publicstaticvoid setUpBeforeClass() throws Exception { // 1、读取配置文件 String sqlMapConfig = "SqlMapConfig.xml"; InputStream is = Resources.getResourceAsStream(sqlMapConfig); // 2、根据配置文件创建SqlSessionFactory sqlSessionFactory= new SqlSessionFactoryBuilder().build(is); }
@Test publicvoid testFindUserById() { // 创建SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); // 通过SqlSession,获取mapper接口的动态代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 调用mapper对象的方法 User user = userMapper.findUserById(1); System.out.println(user); // 关闭SqlSession sqlSession.close();
} |
6 Mybatis全局配置文件
SqlMapConfig.xml是mybatis的全局配置文件,它的名称可以是任意命名的。
6.1 全部配置内容
SqlMapConfig.xml的配置内容和顺序如下(顺序不能乱):
Properties(属性)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境信息集合)
environment(单个环境信息)
transactionManager(事物)
dataSource(数据源)
mappers(映射器)
6.2 常用配置详解
6.2.1 Properties
SqlMapConfig.xml文件中可以引用java属性文件中的配置信息
db.properties配置信息如下:
db.driver=oracle.jdbc.OracleDriver
db.url=jdbc:oracle:thin:@localhost:1521:XE
db.username=xiaoming
db.password=root
SqlMapConfig.xml使用properties标签后,如下所示:
<!-- 通过properties标签,读取java配置文件的内容 -->
<propertiesresource="db.properties"/>
<!-- 配置mybatis的环境信息 -->
<environmentsdefault="development">
<environmentid="development">
<!-- 配置JDBC事务控制,由mybatis进行管理 -->
<transactionManagertype="JDBC"></transactionManager>
<!-- 配置数据源,采用dbcp连接池 -->
<dataSourcetype="POOLED">
<propertyname="driver"value="${db.driver}"/>
<propertyname="url"value="${db.url}"/>
<propertyname="username"value="${db.username}"/>
<propertyname="password"value="${db.password}"/>
</dataSource>
</environment>
</environments>
使用${},可以引用已经加载的java配置文件中的信息。
注意:mybatis将按照下面的顺序加载属性:
u Properties标签体内定义的属性首先被读取
u Properties引用的属性会被读取,如果发现上面已经有同名的属性了,那后面会覆盖前面的值
u parameterType接收的值会最后被读取,如果发现上面已经有同名的属性了,那后面会覆盖前面的值
所以说,mybatis读取属性的顺序由高到低分别是:parameterType接收的属性值、properties引用的属性、properties标签内定义的属性。
6.2.2 Settings
mybatis全局配置参数,全局参数将会影响mybatis的运行行为。
详细参见“mybatis学习资料/mybatis-settings.xlsx”文件
6.2.3 typeAliases
别名是使用是为了在映射文件中,更方便的去指定入参和结果集的类型,不再用写很长的一段全限定名。
6.2.3.1 mybatis支持的别名
别名 |
映射的类型 |
_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 |
6.2.3.2 自定义别名
SqlMapConfig.xml配置信息如下:
<!-- 定义别名 -->
<typeAliases>
<!-- 单个定义别名 -->
<typeAliastype="com.mybatis.po.User"alias="user"/>
<!-- 批量定义别名(推荐) -->
<!-- [name]:指定批量定义别名的类包,别名为类名(首字母大小写都可) -->
<packagename="com.mybatis.po"/>
</typeAliases>
6.2.4 mappers
6.2.4.1 <mapper resource=’’/>
使用相对于类路径的资源
如:<mapper resource="sqlmap/User.xml" />
6.2.4.2 <mapper url=’’/>
使用完全限定路径
如:<mapper url="file:///D:workspace_spingmvcmybatis_01configsqlmapUser.xml" />
6.2.4.3 <mapper class=’’/>
使用mapper接口的全限定名
如:<mapper class="com.mybatis.mapper.UserMapper"/>
注意:此种方法要求mapper接口和mapper映射文件要名称相同,且放到同一个目录下;
6.2.4.4 <package name=’’/>(推荐)
注册指定包下的所有映射文件
如:<package name="com.mybatis.mapper"/>
注意:此种方法要求mapper接口和mapper映射文件要名称相同,且放到同一个目录下;
7 Mybatis映射文件(核心)
7.1 输入映射
7.1.1 ParameterType
指定输入参数的java类型,可以使用别名或者类的全限定名。它可以接收简单类型、POJO、HashMap。
7.1.1.1 传递简单类型
参考入门需求:根据用户ID查询用户信息。
7.1.1.2 传递POJO对象
参考入门需求:添加用户。
7.1.1.3 传递POJO包装对象
开发中通过pojo传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
7.1.1.3.1 需求
综合查询用户信息,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息)。
7.1.1.3.2 定义包装对象
一般User.java类要和数据表表字段一致,最好不要在这里面添加其他字段,如果使用mybatis的逆向工程时,会根据表结构,生成po类,如果在po类中扩展字段,此时会被覆盖掉。
所以针对要扩展的po类,我们需要创建一个扩展类,来继承它。
定义POJO包装类:
7.1.1.3.3 编写Mapper接口
//通过包装类来进行复杂的用户信息综合查询
public List<UserExt> findUserList(UserQueryVO userQueryVO);
7.1.1.3.4 编写mapper映射文件
<!-- 通过包装类来进行复杂的用户信息综合查询 -->
<selectid="findUserList"parameterType="userQueryVO"resultType="userExt">
SELECT * FROM USER WHERE sex=#{userExt.sex} AND username LIKE '%${userExt.username}%'
</select>
注意:入参的类型变为UserQueryVO、结果集的类型变为UserExt,#{}里面的参数变为UserQueryVO对象中的userExt属性的sex和username子属性。
7.1.1.3.5 编写测试代码
7.1.1.4 传递HashMap(练习)
同传递POJO对象一样,map的key相当于pojo的属性。
7.1.1.4.1 映射文件
<!-- 传递hashmap综合查询用户信息 -->
<selectid="findUserByHashmap"parameterType="hashmap"resultType="user">
select * from user where id=#{id} and username like '%${username}%'
</select>
上边红色标注的是hashmap的key。
7.1.1.4.2 测试代码
Publicvoid testFindUserByHashmap()throws Exception{
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//构造查询条件Hashmap对象
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("id", 1);
map.put("username", "管理员");
//传递Hashmap对象查询用户列表
List<User>list = userMapper.findUserByHashmap(map);
//关闭session
session.close();
}
异常测试:
传递的map中的key和sql中解析的key不一致。
测试结果没有报错,只是通过key获取值为空。
7.2 输出映射
7.2.1 resultType
先带着同学们看下原先resultType作为输出结果映射时,它的特点,如何再把列名改为别名,看看是否还能不能映射成功。
7.2.1.1 使用方法
使用resultType进行结果映射时,查询的列名和映射的pojo属性名完全一致,该列才能映射成功。
如果查询的列名和映射的pojo属性名全部不一致,则不会创建pojo对象;
如果查询的列名和映射的pojo属性名有一个一致,就会创建pojo对象。
7.2.1.2 输出简单类型
当输出结果只有一列时,可以使用ResultType指定简单类型作为输出结果类型。
7.2.1.2.1 需求
查询用户总数。
7.2.1.2.2 Mapper映射文件
<!-- 查询用户信息总数-->
<selectid="findUsersCount"parameterType="UserVo"
resultType="int">
SELECT count(1) FROM USER WHERE sex = #{userExt.sex} AND username LIKE '%${userExt.username}%'
</select>
7.2.1.2.3 Mapper接口
//综合查询用户信息总数。学习:resultType输出简单类型
publicintfindUsersCount(UserVo vo);
7.2.1.2.4 测试代码
7.2.1.3 输出POJO单个对象和列表
注意:输出单个pojo对象和pojo列表(盛放pojo对象)时,mapper映射文件中的resultType的类型是一样的,mapper接口的方法返回值不同。
7.2.1.3.1 Mapper映射文件
Mapper映射文件是同一个
<selectid="findUsersByName"parameterType="java.lang.String"resultType="com.mybatis.po.User">
SELECT * FROM USER WHERE username LIKE '%${value}%'
</select>
7.2.1.3.2 Mapper接口
下面看下mapper接口的不同之处
1、 输出单个pojo对象
//根据用户名称来模糊查询用户信息
public User findUsersByName(String username);
2、 输出pojo列表
//根据用户名称来模糊查询用户信息列表
public List<User> findUsersByName(String username);
总结:同样的mapper映射文件,返回单个对象和对象列表时,mapper接口在生成动态代理的时候,会根据返回值的类型,决定调用selectOne方法还是selectList方法。
7.2.2 resultMap
resultMap可以进行高级结果映射(一对一、一对多映射,后面课程讲解)。
7.2.2.1 使用方法
如果查询出来的列名和属性名不一致,通过定义一个resultMap将列名和pojo属性名之间作一个映射关系。
1、 定义resultMap
2、 使用resultMap作为statement的输出映射类型。
7.2.2.2 需求
把下面SQL的输出结果集进行映射
SELECT id id_,username username_,sex sex_ FROM USER WHERE id = 1
7.2.2.3 Mapper映射文件
定义resultMap:
<!-- 定义resultMap -->
<!--
[id]:定义resultMap的唯一标识
[type]:定义该resultMap最终映射的pojo对象
[id标签]:映射结果集的唯一标识列,如果是多个字段联合唯一,则定义多个id标签
[result标签]:映射结果集的普通列
[column]:SQL查询的列名,如果列有别名,则该处填写别名
[property]:pojo对象的属性名
-->
<resultMaptype="user"id="userResultMap">
<idcolumn="id_"property="id"/>
<resultcolumn="username_"property="username"/>
<resultcolumn="sex_"property="sex"/>
</resultMap>
定义statement:
<!-- 根据ID查询用户信息(学习resultMap) -->
<selectid="findUserByIdResultMap"parameterType="int"resultMap="userResultMap">
SELECT id id_,username username_,sex sex_ FROM USER WHERE id = #{id}
</select>
7.2.2.4 Mapper接口定义
//根据ID查询用户信息(学习resultMap)
public User findUserByIdResultMap(int id);
定义Statement使用resultMap映射结果集时,Mapper接口定义方法的返回值类型为mapper映射文件中resultMap的type类型。
7.2.2.5 测试代码
@Test
publicvoid findUserByIdResultMapTest() {
// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession,获取mapper接口的动态代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 调用mapper对象的方法
User user = userMapper.findUserByIdResultMap(1);
System.out.println(user);
// 关闭SqlSession
sqlSession.close();
}
7.3 动态SQL
通过Mybatis提供的各种动态标签实现动态拼接sql,使得mapper映射文件在编写SQL时更加灵活,方便。常用动态SQL标签有:if、where、foreach;
7.3.1 If和where
- If标签:作为判断入参来使用的,如果符合条件,则把if标签体内的SQL拼接上。
注意:用if进行判断是否为空时,不仅要判断null,也要判断空字符串‘’;
- Where标签:会去掉条件中的第一个and符号。
7.3.1.1 需求
用户信息综合查询列表和用户信息综合查询总数这两个statement的定义使用动态SQL。
7.3.1.2 映射文件
<!-- 综合查询用户信息,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息) -->
<selectid="findUsersByQueryVO"parameterType="com.mybatis.po.QueryUserVO"
resultType="User">
SELECT * FROM USER
<where>
<iftest="userExt != null">
<iftest="userExt.sex != null and userExt.sex != ''">
AND sex = #{userExt.sex}
</if>
<iftest="userExt.username != null and userExt.username != ''">
AND username LIKE '%${userExt.username}%'
</if>
</if>
</where>
</select>
<!-- 综合查询用户信息总数,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息) -->
<selectid="findUsersCount"parameterType="QueryUserVO"
resultType="int">
SELECT count(1) FROM USER
<where>
<iftest="userExt != null">
<iftest="userExt.sex != null and userExt.sex != ''">
AND sex = #{userExt.sex}
</if>
<iftest="userExt.username != null and userExt.username != ''">
AND username LIKE '%${userExt.username}%'
</if>
</if>
</where>
</select>
7.3.1.3 Mapper接口
//通过包装类来进行复杂的用户信息综合查询
public List<UserExt> findUserList(UserQueryVO userQueryVO);
//综合查询用户总数
publicint findUsersCount(UserQueryVO userQueryVO);
7.3.1.4 测试代码
不传用户名:
输出的SQL如下(也不包含用户名):
通过测试可以得知,打印出的SQL语句确实会随着条件的满足情况而不一样。
7.3.2 If Set改造
7.3.2.1.1 映射文件
<!-- 修改 --> <updateid="updateUserSet"parameterType="user"> update t_user <set> <iftest="uname!=null"> uname =#{uname}, </if> <iftest="birthday!=null"> birthday =#{birthday}, </if> <iftest="sex!=null and sex!=0"> sex =#{sex}, </if> <iftest="address!=null"> address =#{address}, </if> </set> where tid=#{tid} </update> |
7.3.2.1.2 Mapper接口
//修改 publicint updateUserSet(User user); |
7.3.2.1.3 测试代码
@Test publicvoid updateUserSet() { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = new User(); user.setSex('1'); user.setUname("小3"); user.setAddress(" 刚修改"); user.setTid(5); userMapper.updateUserSet(user); sqlSession.close(); } |
7.3.3 SQL片段
Mybatis提供了SQL片段的功能,可以提高SQL的可重用性。
7.3.3.1 定义SQL片段
使用sql标签来定义一个SQL片段:
<!-- 定义SQL片段 -->
<!--
[sql标签]:定义一个SQL片段
[id]:SQL片段的唯一标识
建议:
1、SQL片段中的内容最好是以单表来定义
2、如果是查询字段,则不要写上SELECT
3、如果是条件语句,则不要写上WHERE
-->
<sqlid="select_user_where">
<iftest="userExt != null">
<iftest="userExt.sex != null and userExt.sex != ''">
AND sex = #{userExt.sex}
</if>
<iftest="userExt.username != null and userExt.username != ''">
AND username LIKE '%${userExt.username}%'
</if>
</if>
</sql>
7.3.3.2 引用SQL片段
使用<include refid=’’ />来引用SQL片段:
<!-- 根据用户id来查询用户信息(使用SQL片段) -->
<!--
[include标签]:引用已经定义好的SQL片段
[refid]:引用的SQL片段id
-->
<selectid="findUserList"parameterType="userQueryVO"resultType="userExt">
SELECT * FROM USER
<where>
<includerefid="select_user_where"/>
</where>
</select>
<!-- 综合查询用户信息总数,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息) -->
<selectid="findUsersCount"parameterType="QueryUserVO"
resultType="int">
SELECT count(1) FROM USER
<where>
<includerefid="select_user_where"/>
</where>
</select>
7.3.4 Foreach
向sql传递数组或List时,mybatis使用foreach解析数组里的参数并拼接到SQL中。
7.3.4.1 传递pojo对象中的List集合
7.3.4.1.1 需求
在用户查询列表和查询总数的statement中增加多个id输入查询。
7.3.4.1.2 SQL
SELECT * FROM user WHERE id IN (1,10,16)
7.3.4.1.3 定义pojo中的List属性
7.3.4.1.4 映射文件
<!-- [foreach标签]:表示一个foreach循环 -->
<!-- [collection]:集合参数的名称,如果是直接传入集合参数,则该处的参数名称只能填写[list]。 -->
<!-- [item]:每次遍历出来的对象 -->
<!-- [open]:开始遍历时拼接的串 -->
<!-- [close]:结束遍历时拼接的串 -->
<!-- [separator]:遍历出的每个对象之间需要拼接的字符 -->
<iftest="idList != null and idList.size > 0">
<foreachcollection="idList"item="id"open="AND id IN ("close=")"separator=",">
#{id}
</foreach>
</if>
7.3.4.1.5 Mapper接口
//根据用户ID的集合查询用户列表(学习foreach标签之通过POJO对象传ID集合)
public List<UserExt> findUserList(UserQueryVO vo);
7.3.4.1.6 测试代码
@Test
publicvoid testFindUserList() {
// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession,获取mapper接口的动态代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 构造QueryUserVO对象
QueryUserVO vo = new QueryUserVO();
// UserExt ext = new UserExt();
// ext.setUsername("小明");
// ext.setSex("1");
// vo.setUserExt(ext);
// 创建用户ID集合,然后设置到QueryUserVO对象中
List<Integer> idList = new ArrayList<Integer>();
idList.add(1);
idList.add(10);
idList.add(16);
vo.setIdList(idList);
// 调用mapper代理对象的方法
List<UserExt> list = mapper.findUserList(vo);
System.out.println(list);
// 关闭SqlSession
sqlSession.close();
}
8 关联查询映射
8.1 分析数据模型
8.1.1 思路
1、 每张表记录的数据内容
分模块对每张表记录的内容进行熟悉,相当于你学习系统需求(功能)的过程。
2、 每张表重要的字段
主键、外键、非空字段
3、 数据库级别表与表的关系
外键关系
4、 表与表之间的业务关系
在分析表与表之间的业务关系时一定要建立 在某个业务意义基础上去分析。
8.1.2 图形分析
8.1.3 数据库表之间有外键关系的业务关系
user和orders:
user---->orders:一个用户可以创建多个订单,一对多
orders--->user:一个订单只由一个用户创建,一对一
orders和orderdetail:
orders-àorderdetail:一个订单可以包括 多个订单明细,因为一个订单可以购买多个商品,每个商品的购买信息在orderdetail记录,一对多关系
orderdetail--> orders:一个订单明细只能包括在一个订单中,一对一
orderdetail和itesm:
orderdetail---》itesms:一个订单明细只对应一个商品信息,一对一
items--> orderdetail:一个商品可以包括在多个订单明细 ,一对多
8.1.4 数据库表之间没有外键关系的业务关系
Orders和items:
这两张表没有直接的外键关系,通过业务及数据库的间接关系分析出它们是多对多的关系。
Ordersà orderdetail –>items:一个订单可以有多个订单明细,一个订单明细对应一个商品,所以一个订单对应多个商品
Items-àorderdetailàorders:一个商品可以对应多个订单明细,一个订单明细对应一个订单,所以一个商品对应多个订单
User和items:
这两张表没有直接的外键关系,通过业务及数据库的间接关系分析出它们是多对多的关系。
Useràordersàorderdetailàitems:一个用户有多个订单,一个订单有多个订单明细、一个订单明细对应一个商品,所以一个用户对应多个商品
Itemsàorderdetailàordersàuser:一个商品对应多个订单明细,一个订单明细对应一个订单,一个订单对应一个用户,所以一个商品对应多个用户
8.2 一对一查询
8.2.1 需求
查询订单信息,关联查询创建订单的用户信息
8.2.2 SQL语句
确定查询的主表:订单表
确定查询的关联表:用户表
SELECT
o.id,
o.order_Number,
o.createtime,
o.describer,
o.user_id
userId,
u.uname,
u.address
FROM orders o,t_user u
WHERE o.user_id=u.tid;
8.2.3 resultType
复杂查询时,单表对应的po类已不能满足输出结果集的映射。
所以要根据需求建立一个扩展类来作为resultType的类型。
8.2.3.1 创建po类
//通过此类映射订单和用户查询的结果,让此类继承包括 字段较多的pojo类
publicclass OrdersExt extends Orders{
//添加用户属性
/*USER.username,USER.address */
private String username;
private String address;
}
8.2.3.2 编写mapper接口
创建OrdersMapper接口类,在类中添加以下内容:
// 进行订单信息查询,包括用户的名称和地址信息
public List<OrdersExt> findOrdersUser();
8.2.3.3 编写映射文件
<mappernamespace="com.mybatis.mapper.OrdersMapper">
<!-- 定义查询订单表列名的SQL片段 -->
<sqlid="select_orders">
Orders.id,
Orders.user_id,
orders.number,
orders.createtime,
orders.note
</sql>
<!-- 定义查询用户表列名的SQL片段 -->
<sqlid="select_user">
user.username,
user.address
</sql>
<!-- 进行订单信息查询,包括用户的名称和地址信息 -->
<selectid="findOrdersUser"resultType="OrdersExt">
Select
<includerefid="select_orders"/>
<includerefid="select_user"></include>
from orders,user
where orders.user_id = user.id
</select>
</mapper>
8.2.3.4 加载映射文件
<!-- 批量加载mapper文件,需要mapper接口文件和mapper映射文件名称相同且在同一个包下 -->
<packagename="com.mybatis.mapper"/>
8.2.3.5 编写测试代码
@Test
publicvoidtestFindOrdersUser() {
// 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession构造usermapper的代理对象
OrdersMapper orders qlSession.getMapper(OrdersMapper.class);
// 调用usermapper的方法
// 释放SqlSession
sqlSession.close();
}
8.2.4 resultMap
8.2.4.1 修改po类
在Orders类中,添加User对象
publicclass Orders {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
//用户信息
privateUseruser;
8.2.4.2 编写mapper接口
// 进行订单信息查询,包括用户的名称和地址信息(resultMap)
public List<OrdersExt> findOrdersUserRstMap();
8.2.4.3 编写映射文件
<!-- 进行订单信息查询,包括用户的名称和地址信息 (ResultMap) -->
<selectid="findOrdersUserRstMap"resultMap="OrdersUserRstMap">
Select
<includerefid="select_orders"/>
,
<includerefid="select_user"></include>
from orders,user
where orders.user_id = user.id
</select>
<!-- 定义orderUserResultMap -->
<resultMaptype="com.mybatis.po.Orders"id="OrdersUserRstMap">
<idcolumn="id"property="id"/>
<resultcolumn="user_id"property="userId"/>
<resultcolumn="number"property="number"/>
<resultcolumn="createtime"property="createtime"/>
<resultcolumn="note"property="note"/>
<!-- 映射一对一关联关系的用户对象-->
<!--
property:指定关联对象要映射到Orders的哪个属性上
javaType:指定关联对象所要映射的java类型
-->
<!-- id标签:指定关联对象结果集的唯一标识,很重要,不写不会报错,但是会影响性能 -->
<associationproperty="user"javaType="com.mybatis.po.User">
<idcolumn="user_id"property="id"/>
<resultcolumn="username"property="username"/>
<resultcolumn="address"property="address"/>
</association>
</resultMap>
8.2.4.4 编写测试代码
@Test
publicvoid testFindOrdersUserRstMap() {
// 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession构造usermapper的代理对象
OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
// 调用usermapper的方法
List<Orders> list = ordersMapper.findOrdersUserRstMap();
//此处我们采用debug模式来跟踪代码,然后验证结果集是否正确
System.out.println(list);
// 释放SqlSession
sqlSession.close();
}
8.2.5 一对一小结
实现一对一查询:
resultType:使用resultType实现较为简单,如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。
如果没有查询结果的特殊要求建议使用resultType。
resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射pojo的对象属性中。
resultMap可以实现延迟加载,resultType无法实现延迟加载。
8.3 一对多查询
一对多查询和一对一查询的配置基本类似。只是如果使用resultMap的话,映射一对多关联关系要使用collection标签。
8.3.1 需求
查询订单信息及订单明细信息
8.3.2 SQL语句
确定主查询表:订单表
确定关联查询表:订单明细表
在一对一查询基础上添加订单明细表关联即可。
SELECT
o.id,
o.order_Number,
o.createtime,
o.describer,
o.user_id,
u.uname,
u.address,
d.id detailId,
d.product_num
FROM orders o,t_user u ,orderdetail d
WHERE o.user_id=u.tid
AND o.id=d.order_id
分析
使用resultType将上边的 查询结果映射到pojo中,订单信息将会重复。
要求:
对orders映射不能出现重复记录。
在orders.java类中添加List<Orderdetail>detailList属性。
最终会将订单信息映射到orders中,订单所对应的订单明细映射到orders中的detailList属性中。
映射成的orders记录数为两条(orders信息不重复)
每个orders中的detailList属性存储了该订单所对应的订单明细集合。
8.3.3 修改PO类
在Orders类中添加以下属性,并提供get/set方法:
//订单明细
private List<Orderdetail>detailList;
8.3.4 编写mapper接口
// 查询订单信息及订单明细信息(一对多映射之使用resultMap)
public List<Orders> findOrdersAndOrderdetailRstMap();
8.3.5 编写映射文件
<!-- 定义OrdersAndOrderdetailRstMap -->
<!-- extends:继承已有的ResultMap,值为继承的ResultMap的唯一标示 -->
<resultMaptype="Orders"id="OrdersAndOrderdetailRstMap"
extends="OrdersUserRstMap">
<!-- 映射关联关系(一对多) -->
<!-- collection标签:定义一个一对多关系
ofType:指定该集合参数所映射的类型
-->
<collectionproperty="detailList"ofType="Orderdetail">
<idcolumn="detail_id"property="id"/>
<resultcolumn="items_id"property="itemsId"/>
<resultcolumn="items_num"property="itemsNum"/>
</collection>
</resultMap>
<!-- 查询订单信息,包括用户名称、用户地址,订单商品信息(嵌套结果) -->
<selectid="findOrdersAndOrderdetailRstMap"resultMap="OrdersAndOrderdetailRstMap">
Select
<includerefid="select_orders"/>
,
<includerefid="select_user"/>
,
orderdetail.id detail_id,
orderdetail.items_id,
orderdetail.items_num
from orders,user,orderdetail
where orders.user_id = user.id
and
orders.id = orderdetail.orders_id
</select>
resultMap的extends属性:可以用此属性来继承一个已有的resultmap。但是它继承的resultMap的type和它本身的type要保持一致。
8.3.6 编写测试代码
@Test
publicvoid testFindOrdersAndOrderdetailRstMap() {
// 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession构造usermapper的代理对象
OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
// 调用usermapper的方法
List<Orders> list = ordersMapper.findOrdersAndOrderdetailRstMap();
//此处我们采用debug模式来跟踪代码,然后验证结果集是否正确
System.out.println(list);
// 释放SqlSession
sqlSession.close();
}
8.3.7 一对多小结
mybatis使用resultMap的collection对关联查询的多条记录映射到一个list集合属性中。
使用resultType实现:
需要对结果集进行二次处理。
将订单明细映射到orders中的orderdetails中,需要自己处理,使用双重循环遍历,去掉重复记录,将订单明细放在orderdetails中。
8.4 多对多查询
8.4.1 需求
查询用户信息及用户购买的商品信息,要求将关联信息映射到主pojo的pojo属性中
8.4.2 SQL语句
查询主表:user
查询关联表:orders、orderdetail、t_product
select orders.id,
orders.order_number,
orders.createtime,
orders.describer,
orders.user_id,
u.uname,
u.address,
o.id detail_id,
o.product_num,
p.id product_id,
p.p_name,
p.description,
p.price
from orders, tt_user u,orderdetail o,t_product p
where orders.user_id = u.tid and orders.id=o.order_id and o.product_id=p.id
8.4.3 映射思路
将用户信息映射到user中。
在user类中添加订单列表属性List<Orders> orderslist,将用户创建的订单映射到orderslist
在Orders中添加订单明细列表属性List<Orderdetail> detailList,将订单的明细映射到detailList
在Orderdetail中添加Items属性,将订单明细所对应的商品映射到product
8.4.4 修改PO类
在user类中添加List<Orders> ordersList 属性
// 订单信息
private List<Orders>ordersList;
在Orders类中添加List<Orderdetail>属性
//订单明细
private List<Orderdetail>detailList;
在Orderdetail类中添加Items属性
//商品信息
private Product product;
8.4.5 编写mapper接口
//查询用户及用户购买商品信息(多对多映射之使用resultMap)
public List<User> findUserAndItemsRstMap();
8.4.6 编写映射文件
<!-- 定义UserAndItemsRstMap -->
<resultMap type="com.mybatis.po.User" id="userProductResMap">
<!-- user映射 -->
<id column="user_id" property="tid"/>
<result column="uname" property="uname"/>
<result column="address" property="address"/>
<!-- 订单 一个用户对应多个订单 -->
<collection property="orders" ofType="com.mybatis.po.Orders">
<id column="id" property="id"/>
<result column="ORDER_NUMBER" property="orderNumber"/>
<result column="createtime" property="createtime"/>
<result column="describer" property="describer"/>
<result column="user_Id" property="userId"/>
<!-- 一个订单有多个明细 -->
<collection property="orderDetails" ofType="com.mybatis.po.OrderDetail">
<id column="detail_id" property="id"/>
<result column="product_num" property="productNum"/>
<!-- 一个订单对应一个产品 -->
<association property="product" javaType="Product">
<id column="product_id" property="id"/>
<result column="p_name" property="pName"/>
<result column="description" property="description"/>
<result column="price" property="price"/>
</association>
</collection>
</collection>
</resultMap>
<!-- 查询用户及用户购买商品信息(多对多映射之使用resultMap) -->
<selectid="findUserAndItemsRstMap"resultMap="UserAndItemsRstMap">
Select
<includerefid="select_orders"/>
,
<includerefid="select_user"/>
,
<includerefid="select_orderdetail"></include>
,
items.name items_name,
items.detail items_detail
from
orders,user,orderdetail,items
where orders.user_id = user.id
and
orders.id = orderdetail.orders_id
and orderdetail.items_id = items.id
</select>
8.4.7 编写测试代码
@Test
publicvoid testFindUserAndItemsRstMap() {
// 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession构造usermapper的代理对象
OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
// 调用usermapper的方法
List<User> list = ordersMapper.findUserAndItemsRstMap();
// 此处我们采用debug模式来跟踪代码,然后验证结果集是否正确
System.out.println(list);
// 释放SqlSession
sqlSession.close();
}
8.4.8 多对多查询小结
将查询用户购买的商品信息明细清单,(用户名、用户地址、购买商品名称、购买商品时间、购买商品数量)
针对上边的需求就使用resultType将查询到的记录映射到一个扩展的pojo中,很简单实现明细清单的功能。
一对多是多对多的特例,如下需求:
查询用户购买的商品信息,用户和商品的关系是多对多关系。
需求1:
查询字段:用户账号、用户名称、用户性别、商品名称、商品价格(最常见)
企业开发中常见明细列表,用户购买商品明细列表,
使用resultType将上边查询列映射到pojo输出。
需求2:
查询字段:用户账号、用户名称、购买商品数量、商品明细(鼠标移上显示明细)
使用resultMap将用户购买的商品明细列表映射到user对象中。
总结:
使用resultMap是针对那些对查询结果映射有特殊要求的功能,,比如特殊要求映射成list中包括 多个list。
8.5 高级映射总结
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集合中。
9 延迟加载
9.1 什么是延迟加载
resultMap中的association和collection标签具有延迟加载的功能。
延迟加载的意思是说,在关联查询时,利用延迟加载,先加载主信息。需要关联信息时再去按需加载关联信息。这样会大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
9.2 设置延迟加载
Mybatis默认是不开启延迟加载功能的,我们需要手动开启。
需要在SqlMapConfig.xml文件中,在<settings>标签中开启延迟加载功能。
lazyLoadingEnabled、aggressiveLazyLoading
设置项 |
描述 |
允许值 |
默认值 |
lazyLoadingEnabled |
全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。 |
true | false |
false |
aggressiveLazyLoading |
当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。 |
true | false |
true |
9.3 使用association进行延迟加载
9.3.1 需求
查询订单并且关联查询用户信息(对用户信息的加载要求是按需加载)
9.3.2 编写映射文件
需要定义两个mapper的方法对应的statement。
1、只查询订单信息
SELECT * FROM orders
在查询订单的statement中使用association去延迟加载(执行)下边的satatement(关联查询用户信息)
<!-- 定义OrdersUserLazyLoadingRstMap -->
<resultMaptype="com.mybatis.po.Orders"id="OrdersUserLazyLoadingRstMap">
<idcolumn="id"property="id"/>
<resultcolumn="user_id"property="userId"/>
<resultcolumn="number"property="number"/>
<resultcolumn="createtime"property="createtime"/>
<resultcolumn="note"property="note"/>
<!-- 延迟加载用户信息 -->
<!-- select:指定延迟加载需要执行的statement的id(是根据user_id查询用户信息的statement)
我们使用UserMapper.xml中的findUserById完成根据用户ID(user_id)查询用户信息
如果findUserById不在本mapper中,前边需要加namespace
-->
<!-- column:主信息表中需要关联查询的列,此处是user_id -->
<associationproperty="user"select="com.mybatis.mapper.UserMapper.findUserById"column="user_id"></association>
</resultMap>
<!-- 查询订单信息,延迟加载关联查询的用户信息 -->
<selectid="findOrdersUserLazyLoading"resultMap="OrdersUserLazyLoadingRstMap">
SELECT * FROM orders
</select>
2、关联查询用户信息
通过上边查询到的订单信息中user_id去关联查询用户信息
使用UserMapper.xml中的findUserById
<selectid="findUserById"parameterType="int"
resultType="com.mybatis.po.User">
SELECT * FROM user WHERE id = #{id}
</select>
上边先去执行findOrdersUserLazyLoading,当需要去查询用户的时候再去执行findUserById,通过resultMap的定义将延迟加载执行配置起来。
9.3.3 加载映射文件
<!-- 批量加载mapper文件,需要mapper接口文件和mapper映射文件名称相同且在同一个包下 -->
<packagename="com.mybatis.mapper"/>
9.3.4 编写mapper接口
// 查询订单信息,延迟加载关联查询的用户信息
public List<Orders>findOrdersUserLazyLoading();
9.3.5 编写测试代码
思路:
1、执行上边mapper方法(findOrdersUserLazyLoading),内部去调用com.mybatis.mapper.OrdersMapper中的findOrdersUserLazyLoading只查询orders信息(单表)。
2、在程序中去遍历上一步骤查询出的List<Orders>,当我们调用Orders中的getUser方法时,开始进行延迟加载。
3、执行延迟加载,去调用UserMapper.xml中findUserbyId这个方法获取用户信息。
@Test
publicvoidtestFindOrdersUserLazyLoading() {
// 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession构造usermapper的代理对象
OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
// 调用usermapper的方法
List<Orders> list = ordersMapper.findOrdersUserLazyLoading();
for(Orders orders : list){
System.out.println(orders.getUser());
}
// 释放SqlSession
sqlSession.close();
}
9.4 延迟加载思考
不使用mybatis提供的association及collection中的延迟加载功能,如何实现延迟加载??
实现方法如下:
定义两个mapper方法:
1、查询订单列表
2、根据用户id查询用户信息
实现思路:
先去查询第一个mapper方法,获取订单信息列表
在程序中(service),按需去调用第二个mapper方法去查询用户信息。
总之:
使用延迟加载方法,先去查询简单的sql(最好单表,也可以关联查询),再去按需要加载关联查询的其它信息。
10 查询缓存
10.1mybatis缓存分析
mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
10.2一级缓存
10.2.1 原理
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
Mybatis默认支持一级缓存。
10.2.2 测试1
@Test
publicvoid testOneLevelCache() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
User user1 = mapper.findUserById(1);
System.out.println(user1);
// 第二次查询ID为1的用户
User user2 = mapper.findUserById(1);
System.out.println(user2);
sqlSession.close();
}
只输出一次SQL:
10.2.3 测试2
@Test
publicvoid testOneLevelCache() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
User user1 = mapper.findUserById(1);
System.out.println(user1);
User user = new User();
//执行增删改操作,清空缓存
mapper.insertUser(user);
// 第二次查询ID为1的用户
User user2 = mapper.findUserById(1);
System.out.println(user2);
sqlSession.close();
}
中间执行了commit操作,同样的查询SQL输出两次:
10.2.4 应用
正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
一个service方法中包括 很多mapper方法调用。
service{
//开始执行时,开启事务,创建SqlSession对象
//第一次调用mapper的方法findUserById(1)
//第二次调用mapper的方法findUserById(1),从一级缓存中取数据
//方法结束,sqlSession关闭
}
如果是执行两次service调用查询相同 的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空。
10.3二级缓存
10.3.1 原理
下图是多个sqlSession请求UserMapper的二级缓存图解。
二级缓存是mapper级别的。
第一次调用mapper下的SQL去查询用户信息。查询到的信息会存到该mapper对应的二级缓存区域内。
第二次调用相同namespace下的mapper映射文件中相同的SQL去查询用户信息。会去对应的二级缓存内取结果。
如果调用相同namespace下的mapper映射文件中的增删改SQL,并执行了commit操作。此时会清空该namespace下的二级缓存。
10.3.2 开启二级缓存
Mybatis默认是没有开启二级缓存
1、 在核心配置文件SqlMapConfig.xml中加入以下内容(开启二级缓存总开关):
在settings标签中添加以下内容:
<!-- 开启二级缓存总开关 -->
<settingname="cacheEnabled"value="true"/>
2、 在UserMapper映射文件中,加入以下内容,开启二级缓存:
<!-- 开启本mapper下的namespace的二级缓存,默认使用的是mybatis提供的PerpetualCache -->
<cache></cache>
10.3.3 实现序列化
由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,所以需要给缓存的对象执行序列化。
如果该类存在父类,那么父类也要实现序列化。
10.3.4 测试1
@Test
publicvoid testTwoLevelCache() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
// 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
User user1 = mapper1.findUserById(1);
System.out.println(user1);
// 关闭SqlSession1
sqlSession1.close();
// 第二次查询ID为1的用户
User user2 = mapper2.findUserById(1);
System.out.println(user2);
// 关闭SqlSession2
sqlSession2.close();
}
SQL输出结果:
Cache Hit Radio: 缓存命中率
第一次缓存中没有记录,则命中率0.0;
第二次缓存中有记录,则命中率0.5(访问两次,有一次命中)
10.3.5 测试2
@Test
publicvoid testTwoLevelCache() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
// 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
User user1 = mapper1.findUserById(1);
System.out.println(user1);
// 关闭SqlSession1
sqlSession1.close();
//修改查询出来的user1对象,作为插入语句的参数
mapper3.insertUser(user1);
// 提交事务
sqlSession3.commit();
// 关闭SqlSession3
sqlSession3.close();
// 第二次查询ID为1的用户
User user2 = mapper2.findUserById(1);
System.out.println(user2);
// 关闭SqlSession2
sqlSession2.close();
}
SQL输出结果:
根据SQL分析,确实是清空了二级缓存了。
10.3.6 禁用二级缓存
该statement中设置userCache=false,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。
<selectid="findUserById"parameterType="int"
resultType="com.mybatis.po.User"useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
10.3.7 刷新二级缓存
该statement中设置flushCache=true可以刷新当前的二级缓存,默认情况下如果是select语句,那么flushCache是false。如果是insert、update、delete语句,那么flushCache是true。
如果查询语句设置成true,那么每次查询都是去数据库查询,即意味着该查询的二级缓存失效。
如果查询语句设置成false,即使用二级缓存,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现脏读。
flushCache设置如下:
<selectid="findUserById"parameterType="int"
resultType="com.mybatis.po.User"useCache="true"flushCache="true">
SELECT * FROM user WHERE id = #{id}
</select>