框架(Framework)
MyBatis
3.2.2. 创建config.properties文件 3-14
1. MyBatis 简介
1.1. MyBatis 概述?
1.1.1. MyBatis 是什么?
MyBatis 由apache的ibatis演变而来,可以从如下几个方面加强理解与应用。
1) 软件框架(Framework): 用于解决软件中的通用型(共性)问题
2) 软件中的优秀的持久层框架(数据访问层):用于更好解决数据持久化问题
课后了解:JAVA生态项目中常用的持久层框架
hibernate (ORM 框架) :用于解决数据的持久化问题(数据库操作)
1.1.2. MyBatis 应用场景
MyBatis主要应用于Java技术生态项目的研发。例如:
1) Java传统桌面型项目(例如传统的计费系统等)
2) Java web 互联网项目(例如电商项目,企业互联网业务系统)
1.1.3. MyBatis 应用优势
思考:
1) 互联网项目的硬性要求是什么?(快速交付)
2) 互联网项目的如何进行技术选型?
第一:稳定,可靠,性能;
第二:学习成本。
第三:是否可持续发展,社区支持的力度)
思考:
传统JDBC开发劣势?
1) 编程步骤,参数配置及读取相关繁琐(配置信息的读取,都需要自己写代码)
2) 数据直接映射硬编码的强度会比较高(代码冗余相对较高,维护起来困难)
3) 数据关系映射的实现相对困难?(one2one,one2many,many2many)
思考:框架MyBatis开发优势
1) 封装了JDBC共性,简化了代码的编写,提高了代码的开发速度,以及可维护性。
2) 合理的架构设计,提高了系统的稳定性,访问性能,可扩展性。
框架MyBatis开发劣势
1) SQL语句编写的工作量相对较大。(相对hibernate框架)
2) SQL语句依赖于数据库,移植性相对较差。(不是最大劣势)
1.2. MyBatis 架构体系?
对于任何一个持久层框架,都应该具备接口服务,数据处理服务,基础服务等相关功能,MyBatis也不例外,它会具体如下几个层结构.
1.2.1. MyBatis接口应用层
MyBatis接口应用层主要负责对外提供应用服务,例如
1) 数据查询
2) 数据修改
3) 数据删除
4) 数据插入
1.2.2. MyBatis数据处理层
MyBatis数据处理层主要负责处理数据访问问题
1) SQL参数映射(Dao方法参数与映射文件中#{}表达式映射)
2) SQL解析(语法,语义) 例如: select * from blog where id=#{id}
3) SQL 执行(将sql发送到数据库端执行)
4) SQL 结果映射(例如将ResultSet中的数据存到map)
5) ....
1.2.3. MyBatis基础服务层
MyBatis基础服务层主要负责提供如下几个方面的服务:
1) 连接服务 (配置连接池,)
2) 事务服务(保证数据的原子性,一致性,隔离性,一致性。)
3) 缓存服务(更好的提高查询性能)
4) 配置服务 (别名配置,映射配置,...,日志配置,....)
5) .....
1.3. MyBatis 核心组件?
1.3.1. MyBatis核心文件
MyBatis 核心应用组件:
1. 配置文件(提供基础配置信息,例如连接配置,缓存配置,映射配置)
2. 映射文件(定义SQL映射)
1.3.2. MyBatis 核心API
MyBatis 项目中核心API:
1. SqlSessionFactoryBuilder (负责读取配置文件,创建SqlSessionFactory对象)
2. SqlSessionFactory(负责创建SqlSession对象)
3. SqlSession(负责连接的维护,事务的处理,类似JDBC中的Connection)
4. .....
思考:
1)核心组件在应用中的一个角色定位?
2)应用底层会用到哪些设计模式?建造模式,工厂模式
2. MyBatis 编程基础
2.1. MyBatis 基本步骤
2.1.1. JDBC 编程步骤回顾
JDBC 编程的基本步骤如下:
1) 加载驱动程序Driver (Class.forName(“com.mysql.jdbc.Driver”))
2) 建立连接Connection (DriverManager.getConnection(url,username,password))
3) 创建Statement (conn.createStatement())
4) 发送sql (stmt.executeUpdate(sql))
5) 处理结果ResultSet (while(rs.next))
6) 释放资源(close)
2.1.2. MyBaits 编程步骤分析
MyBatis 项目中一般的编程步骤:
Step01:创建maven 桌面项目(Java 项目)
Step02:在pom.xml文件中添加Mybatis依赖和mysql驱动依赖。
Step03:创建mybatis 配置文件,映射文件
Step04:配置数据访问(配置文件),SQL映射(映射文件)
Step05:创建MyBatis API(例如SqlSession)对象,执行SQL操作.
思考?
1) 数据库连不上,可能存在哪些问题?
a) 检测url,用户名,密码
b) 检测端口号(port)
c) id地址是否能ping 通
d) 检测驱动程序(有可能依赖的驱动程序与数据库版本不兼容)
2) mybatis 配置文件的名字有要求吗?(只要符合标识符规范即可)
3) mybatis 配置文件是要对哪些信息进行配置呢?
4) mybatis 映射文件中主要用于定义哪些内容?(sql元素)
5) MyBatis 执行SQL操作时的一个基本过程是怎样的?
a) step01:MyBatis API调用JDBC API
b) step02:JDBC API 调用数据库驱动程序API
2.2. MyBatis 编程实现
2.2.1. 数据库数据准备
创建数据库,并在库中创建表
create database cgbmybatis character set utf8;
打开数据库
use cgbmybatis
创建表
create table blog(
id int primary key auto_increment,
title varchar(200) not null,
content varchar(500) not null,
createdTime date
) engine=innoDB;
向表中写入数据
insert into blog values (null,'ta','ta...',now());
insert into blog values (null,'tb','tb...',now());
2.2.2. 创建Maven桌面项目
创建maven 桌面项目,并添加依赖(mybatis,mysql驱动)
说明:桌面项目的创建参考doc.tedu.cn中maven配置
添加mybatis依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
添加MySQL驱动依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
添加junit依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
思考:
1) 添加依赖时注意groupId的选择?(选正规的)
2) 添加依赖以后pom.xml文件假如有错什么原因?
检测网络,检测maven配置(setting.xml)
2.2.3. 添加配置及映射文件
在src/main/resources目录下创建配置文件mybatis-configs.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>
<!-- 配置初始化环境(连接) -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///cgb1711"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 配置mapper文件路径 -->
<mappers>
<mapper resource="mapper/BlogMapper.xml"/>
</mappers>
</configuration>
说明:
1)配置文件的头可从官方文档进行拷贝.
2)配置文件元素没有提示是什么原因? 对应的dtd文件取不到
提示信息的配置请参考:http://schema.tedu.cn/proxy/
step02:在src/main/resources/mapper目录下创建映射文件BlogMapper.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">
<mapper namespace="com.jt.blog.BlogDao">
</mapper>
说明:
1) 映射文件的命名空间用于约束元素id的唯一性.
2) 映射文件的格式最好是包结构形式
3) 映射文件内部可以定义很多元素,每个元素必须有一个唯一id,例如select
在mapper文件中添加如下元素:
查询所有blog数据
<select id="findBlogs" resultType="map">
select * from blog
</select>
根据id查询blog数据
<select id="findBlogById" resultType="map">
select *
from blog
where id=#{id}
</select>
限制查询blog元素(此方式参数的传递应用Object[]数组进行封装)
<select id="findPageBlogs"
resultType="map">
select *
from blog
limit #{array[0]},#{array[1]}
</select>
限制查询Blog元素(此方式的参数应用map进行封装,#{}表达式中的内容为map中的key)
<select id="findPageBlogs"
resultType="map">
select *
from blog
limit #{startIndex},#{pageSize}
</select>
向表中插入数据元素定义
方案1:(测试时参数数据可以为Object[]类型数组)
<insert id="insertObject">
insert into blog
(id,title,content,createdTime)
values
(null,#{array[0]},#{array[1]},now())
</insert>
方案2:(测试时参数数据可以为Blog类型对象或Map对象)
<insert id="insertObject">
insert into blog
(id,title,content,createdTime)
values
(null,#{title},#{content},now())
</insert>
修改表中数据元素定义
方案1:(测试时参数传递需要为Object[]类型的数组)
<update id="updateObject">
update blog
set title=#{array[0]},
content=#{array[1]}
where id=#{array[2]}
</update>
方案2 (测试时参数传递可以是map或者blog对象)
<update id="updateObject">
update blog
set title=#{title},
content=#{content}
where id=#{id}
</update>
删除表中元素元素定义
<delete id="deleteObject">
delete
from blog
where id=#{id}
</delete>
2.2.4. 编写代码执行查询测试
基于BlogMapper.xml文件中元素的定义,添加测试类及相关方法.
编写测试类并添加测试方法
public class TestBlog01 {
private SqlSessionFactory factory;
}
测试类中添加初始化factory的方法
@Before
public void init()throws IOException{
//初始化SqlSessionFactory
factory=new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream(
"mybatis-configs.xml"));
}
测试类中添加查询所有blogs的方法
@Test
public void testFindBlogs(){
//1.创建SqlSession对象(相当于创建一个连接)
SqlSession session=factory.openSession();
//2.执行查询操作(selectList("命名空间"+"元素id"))
List<Object> list=session.selectList(
"com.jt.blog.BlogDao.findBlogs");
for(Object o:list){
System.out.println(o);
}
//3.释放资源(类似关闭连接)
session.close();
}
测试类中添加根据id执行查询方法
@Test
public void testFindBlogById(){
//1.创建session对象
SqlSession session=
factory.openSession();
//2.执行sql操作
String statement="com.jt.blog.BlogDao.findBlogById";
Map<String,Object> map=
session.selectOne(statement,1);
System.out.println(map);
//3.释放资源(关闭session对象)
session.close();
}
测试类中添加分页查询方法
@Test
public void testFindPageBlogs(){
//1.创建session对象
SqlSession session=
factory.openSession();
//2.执行sql操作?????
String statement="com.jt.blog.BlogDao.findPageBlogs";
Object parameter=new Object[]{0,4};
List<Map<?,?>> list=
session.selectList(statement, parameter);
for(Map<?,?>map:list){
System.out.println(map);
}
//3.释放资源(关闭session对象)
session.close();
};
测试类中添加插入数据的方法
@Test
public void testInsertObject(){
//1.创建session
SqlSession session=factory.openSession();
//2.执行sql
String statement="com.jt.blog.BlogDao.insertObject";
Object parameter=new Object[]{"te","te..."};
int rows=session.insert(statement, parameter);
System.out.println("insert.rows="+rows);
session.commit();
//3.关闭session
session.close();
}
测试类中添加修改方法
@Test
public void testUpdateObject(){
//1.创建session
SqlSession session=factory.openSession();
//2.执行sql
String statement="com.jt.blog.BlogDao.updateObject";
Object parameter=new Object[]{"taa","taa...",1};
int rows=session.update(statement, parameter);
System.out.println("update.rows="+rows);
session.commit();
//3.关闭session
session.close();
}
测试类中添加删除方法
@Test
public void testDeleteObject(){
//1.创建session
SqlSession session=factory.openSession();
//2.执行sql
String statement="com.jt.blog.BlogDao.deleteObject";
Object parameter=7;
int rows=session.delete(statement, parameter);
session.commit();
System.out.println("delete.rows="+rows);
//3.关闭session
session.close();
}
总结
1)在以上测试时都是调用了SqlSession对象的方法实现了对数据库数据的增删改查操作。例如(selectList,selectOne,insert,update,delete,…..)
2)Mybatis对于insert,update,delete操作的事务控制方式为手动方式,所以再执行完具体操作以后需要执行commit操作。
3. MyBatis 编程进阶
本小节中会从mybatis编程的另一个角度(例如基于接口方式等)实现对数据库中数据的操作.
3.1. MyBatis基于接口实现的基本步骤
Step01: 创建maven桌面项目并添加依赖
Step02: 创建配置文件config.propertis(内容为数据库相关)
Step03: 创建mybatis核心配置文件mybatis-configs.xml文件
Step04: 配置Mybatis基础数据服务(properties,datasource,mapper)
Step05: 创建映射文件BlogMapper.xml
Step06: 创建实体类Blog(与表对应,可用于封装表中数据)
Step07: 创建BlogDao接口,并添加相关方法.
Step08: 配置BlogMapper映射文件,添加相关元素.
Step09: 基于BlogDao接口与映射文件实现CRUD操作
3.2. MyBatis进阶编程实现
3.2.1. 创建Maven桌面项目
创建maven桌面项目并添加依赖
<!-- 添加mybatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
<!-- 添加mysql驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<!-- 添加junit依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
3.2.2. 创建config.properties文件
在src/main/resources目录下创建config.properties文件,此文件中定义
系统中的一些常用配置信息,例如访问数据库的相关信息,其内容如下
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/cgbmybatis
username=root
password=root
3.2.3. 创建mybatis核心配置文件
在src/main/resources目录下创建mybatis-configs.xml文件,并配置数据源等相关信息,数据信息从config.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文件-->
<properties resource="config.properties"/>
<!-- 配置初始化环境(连接) -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
</configuration>
3.2.4. 创建Blog实体对象
创建Blog类实现与数据库中Blog表实现映射.
public class Blog {
private Integer id;
private String title;
private String content;
private Date createdTime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
@Override
public String toString() {
return "Blog [id=" + id + ", title=" + title + ", content=" + content + ", createdTime=" + createdTime + "]";
}
}
3.2.5. 创建BlogDao接口
创建数据访问接口,并添加相关方法
package com.jt.dao;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.jt.entity.Blog;
public interface BlogDao {
/***
* 根据id进行对象查找
* @param id
* @return
*/
Blog findBlogById(Integer id);
List<Blog> findPageBlogs(
@Param("offset")Integer offset,
@Param("pageSize")Integer pageSize);
int insertObject(Blog blog);
int updateObject(Blog blog);
int deleteObject(Integer id);
}
3.2.6. 创建BlogMapper映射文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jt.dao.BlogDao">
</mapper>
BlogMapper文件中添加与BlogDao接口对应的映射元素
添加基于ID进行查询的元素
<select id="findBlogById"
parameterType="int"
resultType="blog">
select *
from blog
where id=#{id}
</select>
添加分页查询元素
<select id="findPageBlogs"
resultType="blog">
select *
from blog
limit #{offset},#{pageSize}
</select>
添加insert元素
<insert id="insertObject"
parameterType="blog">
insert into blog
(id,title,content,createdTime)
values
(null,#{title},#{content},now())
</insert>
添加更新元素
<update id="updateObject"
parameterType="blog">
update blog
set title=#{title},content=#{content}
where id=#{id}
</update>
添加删除元素
<delete id="deleteObject"
parameterType="int">
delete from blog where id=#{id}
</delete>
最后在mybatis-configs.xml中添加BlogMapper文件
3.2.7. 创建单元测试类执行测试
创建单元测试类,并添加相关方法实现基于Dao接口方式的数据库操作.
public class TestBlog01 {
private SqlSessionFactory factory;
@Before
public void init()throws IOException{
factory=new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream(
"mybatis-configs.xml"));
}
@Test
public void testFindBlogById(){
//1.创建session
SqlSession session=factory.openSession();
//2.执行sql
//2.1获取dao对象
BlogDao dao=
session.getMapper(BlogDao.class);
//2.2执行dao中方法
Blog blog=dao.findBlogById(1);
System.out.println(blog);
//3.关闭session
session.close();
}
@Test
public void testFindPageBlogs(){
//1.创建session
SqlSession session=factory.openSession();
//2.执行sql
//2.1获取dao对象
BlogDao dao=
session.getMapper(BlogDao.class);
//2.2执行dao中方法
List<Blog> list=dao.findPageBlogs(0, 2);
for(Blog b:list){
System.out.println(b);
}
//3.关闭session
session.close();
}
@Test
public void testInsertObject(){
//1.创建session
SqlSession session=factory.openSession();
//2.执行sql
//2.1获取dao对象
BlogDao dao=
session.getMapper(BlogDao.class);
//2.2执行dao中方法
Blog blog=new Blog();
blog.setTitle("te");
blog.setContent("te...");
dao.insertObject(blog);
session.commit();
//3.关闭session
session.close();
}
}
3.3. MyBatis业务增强
3.3.1. 动态排序策略
1. 如何按照用户指定业务对数据进行排序?
例如:
1) 博客系统
a) 按照创建时间对博客信息进行排序查询
b) 按照博客访问量对信息进行排序查询
2) 电商系统
a) 按照商品价格对商品信息排序
b) 按照商品销量对商品信息排序
c) 按照商品好评对商品信息排序
解决方案:
1) 写多个sql映射
2) 写一个sql 映射,然后动态传参,借助${}表达式获取参数
例如
接口中方法定义
List<Blog> findBlogs(
@Param("column") String column,
@Param("seq") String seq);
映射文件中的实现
<select id="findBlogs"
resultType="blog">
select *
from blog
order by ${column} ${seq}
</select>
思考:
MyBatis中$与#有什么不同?
${}表达式主要用于获取配置文件数据,DAO接口中的参数信息,当$出现在映射文件的SQL语句中时创建的不是预编译的SQL,而是字符串的拼接,有可能会导致SQL注入问题.所以一般使用$接收dao参数时,这些参数一般是字段名,表名等,例如order by {column}.
#{}表达式主要用户获取DAO中的参数数据,在映射文件的SQL语句中出现#{}表达式,底层会创建预编译的SQL.性能会相对较好.
${}获取DAO参数数据时,参数必须使用@param注解进行修饰或者使用下标或者参数#{param1}形式.
#{}获取DAO参数数据时,假如参数个数多于一个可有选择的使用@param.
3.3.2. ID应用策略
1.保存数据时获取数据在数据库对应的主键值?
例如
1) 订单系统?
保存订单信息时候,获取订单在数据库中的主键id值?因为还要依据这个值保存订单的详情信息(例如OrderItem,…..)
2) 权限系统
保存用户信息时,获取用户信息在数据库中的主键值?还有保存用户和权限关系数据
解决方案: 当对象对应的表中的记录为自增长时,可以采用如下方案
<insert id="insertObject"
parameterType="blog"
useGeneratedKeys="true"
keyProperty="id">
insert into blog
(id,title,content,createdTime)
values
(null,#{title},#{content},now())
</insert>
其中:keyProperty属性用于指定参数中的id属性.
2. 当多线程并发的向表中写入数据时,假如id使用自增长可能存在线程安全问题?
例如:
1) 秒杀系统(电商系统)
2) 订票系统(12306)
3) ....
解决方案:可将自增长的id设置为随机数,当然有些数据库根本就不支持自增长,此时也可以选择随机数.
课堂案例实现:
Step01:数据准备:创建Author表
create table Author(
id varchar(200) primary key,
username varchar(100) unique not null,
password varchar(300) not null,
email varchar(50) unique
)engine=InnoDB;
Step02:基于表创建实体类Author
Step03:基于实体类创建AuthorDao接口
Step04:基于AuthorDao接口创建映射文件
借助mybatis 应用向表中写入数据,主键值要求通过UUID生成.其映射文件参考
<insert id="insertObject" parameterType="author">
<selectKey keyProperty="id"
resultType="string"
order="BEFORE">
select replace(uuid(),'-','')
</selectKey>
insert into author
(id,username,password,email)
values
(#{id},#{username},#{password},#{email})
</insert>
Step05 编写单元测试
说明:
1) 此方式的实现依赖于数据库,存在一定局限性,例如当前这种写法仅适用于mysql
2) 假如不想对数据库有较大依赖,可在应用程序中,通过java API创建随机ID(例如,可借助UUID类产生一个随机串)。
4. MyBatis 高级应用
4.1. 日志配置应用(了解)
4.1.1. 日志基本概述
Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:
1)SLF4J(日志框架标准,类似JDBC标准)
2)Apache Commons Logging
3)Log4j 2 (是log4j的升级版,配置文件升级为xml格式了)
4)Log4j(日志处理库,配置文件格式为.properties)
5)JDK logging
项目中mybatis通常会借助第三方日志库进行日志的处理,例如log4j.
4.1.2. 日志基本实现
配置步骤:(以log4j为例)
1. 添加log4j依赖(一代)
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2. 添加log4j.properties 配置(可以从其它项目中拷贝)
log4j.rootLogger=INFO,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d [%-5p] %c - %m%n
log4j.logger.com.mybatis3=DEBUG
log4j.logger.com.jt=DEBUG
3. 设置mybatis的日志实现(mybatis-configs.xml)
<settings>
<setting name="logImpl" value="log4j"/>
</settings>
其中name属性的值为固定写法,value的值要依托于使用的日志处理库.
说明:课后了解常用的日志处理库.
4.2. 缓存配置应用
4.2.1. 缓存基本概述
1. 缓存是什么? 内存中的一个对象(容器).
2. 缓存对象的作用?提高程序的性能(最主要的的是访问效率)
3. MyBatis 中缓存概述?
MyBatis 框架中提供了非常强大的缓存特性来提高查询性能,通常可将其分为一级缓存(SqlSession级别)和二级缓存(SqlSessionFactory级别)。
4.2.2. 缓存基本实现
MyBatis 一级缓存应用:
MyBatis中一级缓存默认是开启的.不需要任何配置.例如:
测试方案1:(验证一级缓存)
@Test
public void testFirstLevelCache01(){
SqlSession session=factory.openSession();
AuthorDao dao=session.getMapper(AuthorDao.class);
String id="5e5f90ed324e11e8a9b279bf5362f090";
dao.findAuthorById(id);
dao.findAuthorById(id);//本次查询从缓存取
session.commit();
session.close();
}
测试方案1:(验证缓存失效)
验证数据插入导致的缓存失效。
@Test
public void testFirstLevelCache01(){
SqlSession session=factory.openSession();
AuthorDao dao=session.getMapper(AuthorDao.class);
String id="5e5f90ed324e11e8a9b279bf5362f090";
dao.findAuthorById(id);
Author entity=new Author();
entity.setUsername("user-KK");
entity.setPassword("123456");
entity.setEmail("KK@t.com");
dao.insertAuthor(entity);
dao.findAuthorById(id);
session.commit();
session.close();
}
验证更新后的缓存失效。
@Test
public void testFirstLevelCache02(){
SqlSession session=factory.openSession();
AuthorDao dao=session.getMapper(AuthorDao.class);
String id="5e5f90ed324e11e8a9b279bf5362f090";
Author a1=dao.findAuthorById(id);
System.out.println(a1);
Author a=new Author();
a.setId(id);
a.setUsername("user-aaa");
a.setEmail("aaa@t.com");
dao.updateAuthor(a1);
Author a2=dao.findAuthorById(id);
System.out.println(a2);
session.commit();
session.close();
}
验证缓存脏读问题(此问题可通过配置缓存类型为STATEMENT类型解决)
@Test
public void testFirstLevelCache03(){
SqlSession session1=factory.openSession(true);
SqlSession session2=factory.openSession(true);
AuthorDao dao1=session1.getMapper(AuthorDao.class);
AuthorDao dao2=session2.getMapper(AuthorDao.class);
String id="5e5f90ed324e11e8a9b279bf5362f090";
Author a1=dao1.findAuthorById(id);
System.out.println(a1);
Author a=new Author();
a.setId(id);
a.setUsername("user-oo");
a.setEmail("oo@t.com");
int rows=dao2.updateAuthor(a);
session2.close();
Author a2=dao1.findAuthorById(id);
System.out.println(a2);
session1.close();
}
MyBatis:一级缓存应用说明:
1) 默认是开启的(也是应用最多的一种)
2) 其类型为SESSION或STATEMENT两种,默认是SESSION类型
a) 缓存对象的生命周期不同(例如session类型的一级缓存,session关闭就失效.)
b) 其类型的配置可在配置文件中通过这个localCacheScope属性进行配置。
3) 一级缓存在每次更新后都会失效。
4) 一级缓存在事务并发执行时可能会出现脏读,但相对于STATEMENT效率会高一些。
MyBatis 二级缓存应用:
MyBatis 二级缓存默认是没有开启的,需要在映射文件中加上<Cache/>元素
MyBatis 二级缓存应用步骤:
Step01: 修改mybatis核心配置文件,添加缓存设置.
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
Step02: 在映射文件中配置Cache策略.
<cache
eviction="LRU"
flushInterval="60000"
size="512"
readOnly="true"/>
这个表示创建了一个 LRU缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会 导致冲突。其中:
1) eviction 表示回收策略(例如LRU,FIFO等,默认为LRU)
2) flushInterval 表示刷新间隔
3) size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的 可用内存资源数目。默认值是 1024。
4) readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。可读写的缓存会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。
Step03: 使用二级缓存了.
@Test
public void testSecondLevelCache01(){
SqlSession session1=factory.openSession();
SqlSession session2=factory.openSession();
SqlSession session3=factory.openSession();
AuthorDao dao1=session1.getMapper(AuthorDao.class);
AuthorDao dao2=session2.getMapper(AuthorDao.class);
AuthorDao dao3=session3.getMapper(AuthorDao.class);
String id="5e5f90ed324e11e8a9b279bf5362f090";
Author a1=dao1.findAuthorById(id);
System.out.println(a1);
session1.close();//session关闭时,a1指向对象会存储到缓存
Author a2=dao2.findAuthorById(id);//从二级缓存获取
System.out.println(a2);
a2.setUsername("user-tedu-01");
Author a3=dao3.findAuthorById(id);//从二级缓存获取
System.out.println(a3);
System.out.println(a1==a2);//true
System.out.println(a2==a3);//true
//a1,a2,a3指向的是同一个对象
session2.close();
session3.close();
}
FAQ?
1)MyBatis 二级缓存cache元素readOnly值的设置以及底层对象缓存过程.
readOnly说明:
readOnly的值为true时,缓存中保存的是堆内存中对象的引用.每次从缓存取数据都是获得的同一个对象.readOnly为false时,首先会将查询到的对象,拷贝到缓存一份(对象需要实现序列化接口),然后从缓存取数据每次都是执行对象的拷贝.
4.3. 高级映射应用
4.3.1. 高级映射基本概述
MyBatis中的高级映射一般要借助select元素中的resultMap属性进行实现,通过此属性配置实现一对一,一对多等关系映射的实现.
1. 如何实现one2one映射?
2. 如何实现one2many映射?
3. 如何实现many2many映射?
思考:
1) 查询博客信息时,将作者信息也查询出来?(many2one)
2) 查询某个用户的同时,将其对应的博客列表信息也查询出来(one2many)
3) .....
实现方案:
1) 多次单表查询(例如先获取博客信息,再根据外键获取作者信息)
2) 一次关联查询(例如借助 join … on …..等这样的方式实现关联数据的查询)
4.3.2. 高级映射基本实现
课堂案例:(查询博客信息时将作者信息也查询出来)
1) 在blog表中添加一个字段,名字为authorId,类型为string.
alter table blog add authorId varchar(200);
2) 为blog表中的authorId字段赋值,其值为author表中某些记录的主键值.
update blog set authorId=? where id=?
3) 查询某个blog信息,并将其关联的author对象信息查询出来.
方案一:
Step01: BlogDao接口中方法定义:
Map<String,Object> findBlogAuthorById(Integer id);
Step02: BlogMapper中映射元素定义:
<select id="findBlogAuthorById"
resultType="map">
select b.*,a.username,a.email
from blog b join author a
on b.authorId=a.id
where b.id=#{id}
</select>
优势:
1)映射文件中的代码量相对较小,存储简单。
缺陷:
1) 可读性相对较差(map中究竟存储哪里数据,需要通过输出才知道)
2) 从空间使用上可能会有一定的资源浪费
方案二:
Step01:BlogResult 值对象定义(借助此对象封装多表数据)
public class BlogResult {
private Integer id;
private String title;
private String content;
private Date createdTime;
private Author author;
...set/get
}
Step02:BlogDao接口中方法定义:
BlogResult findBlogResultById(Integer id);
Step03:BlogMapper中映射元素定义
<select id="findBlogResultById"
resultMap="blogResult">
select b.*,a.username,a.email
from blog b join author a
on b.authorId=a.id
where b.id=#{id}
</select>
<resultMap id="blogResult" type="com.jt.vo.BlogResult">
<id property="id" column="id"/>
<result property="title" column="title"/>
<result property="content" column="content"/>
<result property="createdTime" column="createdTime"/>
<!-- 关联映射 -->
<association property="author" javaType="author">
<id property="id" column="authorId"/>
<result property="username" column="username"/>
<result property="email" column="email"/>
</association>
</resultMap>
优势:
空间利用率有可能会有所提升,对象可读性有一定成都的提升。
缺陷:
映射文件中的代码量相对较大,映射相对复杂,可读性不太好。
Step04: 单元测试
方案三:(通过两次查询实现)
Step01: 接口中方法定义
BlogResult findBlogResultWithId(Integer id);
Step02: 映射文件元素定义
<resultMap type="com.jt.vo.BlogResult" id="blogResultMap">
<association property="author"
column="authorId"
javaType="author"
select="com.jt.dao.AuthorDao.findObjectById">
</association>
</resultMap>
<select id="findBlogResultWithId" resultMap="blogResultMap">
select * from blog where id=#{id}
</select>
在这个元素映射时假如表中的字段名与类中的属性名相同,可以不使用id,result元素定义具体映射。
此形式的映射关系定义底层会发起两次查询获取blog以及它关联的author数据信息。
Step03: 基于DAO接口方法进行单元测试
方案优势:
1) 映射文件中代码量小了,可维护性提高了。
2) 可通过配置实现延迟加载(在延迟加载小节会讲到),让查询更加灵活。
方案劣势
1) 一个结果需要多次查询,假如所有数据需要立刻加载,性能会相对较低。
4.4. 延迟加载应用
4.4.1. 延迟加载基本概述
延迟加载是按需加载的一种实现策略,一般应用与关联查询中,针对关联对象数据进行延迟加载(何时需要何时加载).以提高内存的使用效率。
4.4.2. 延迟加载基本实现
Step01: 添加依赖(CGLIB依赖,底层延迟加载,构建代理对象时需要)
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
Step02: 配置全局延迟加载(当有些查询不需要延迟加载,可以在查询对应属性中使用fetchType进行覆盖性设置,例如值为lazy表示延迟加载,eager表示实时加载)
<settings>
....
<!-- 配置全局延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 设置按需加载(配合上面的延迟加载属性实现) -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
Step03: 关联映射中就可以直接使用延迟加载了.例如
<resultMap type="com.jt.vo.BlogResult"
id="blogResultMap">
<association property="author"
column="authorId"
javaType="author"
select="com.jt.dao.AuthorDao.findObjectById">
</association>
</resultMap>
<select id="findBlogResultWithId"
resultMap="blogResultMap">
select * from blog where id=#{id}
</select>
当在测试类中只访问blog信息时,不会查询author信息,创建author对象.除非再
在association元素中设置fetchType属性.
基于如上配置编写一个单元测试。
@Test
public void testFindBlogResultWithId(){
//1.创建SqlSession对象
SqlSession session=factory.openSession();
//2.执行sql查询
BlogDao dao=session.getMapper(BlogDao.class);
BlogResult result=dao.findBlogResultWithId(2);
System.out.println(result.getId());
System.out.println(result.getTitle());
//System.out.println(result.getAuthor());
//3.释放资源
session.close();
}
4.5. 动态SQL应用
4.5.1. 动态SQL基本概述
思考:
如何动态删除客户端选择的记录,用户有可能会选择一个记录,也可能会选择多个记录然后进行删除.那么在mybatis的映射文件中该如何配置,如何实现或者说解决这种动态需求问题?
解决方案:借助动态sql实现。
4.5.2. 动态SQL基本实现
动态sql.(以更加灵活的方法解决业务中的数据操作问题)
MyBatis 中常用动态sql元素:foreach,if,where,trim,.....
本小结以foreach为案例,实现一个动态删除操作,其步骤如下:
step01:BlogDao接口中方法定义
int deleteObjectByIds(
@Param("ids") String[] ids);
step02:BlogMapper文件中定义删除元素
<delete id="deleteObjectByIds"
parameterType="string">
delete from blog
where id in <!-- (10,11)-->
<!-- 动态sql forearch
collection 的值为dao传递的参数
item为集合中的某一个元素
separator 用于指定分隔符
-->
<foreach collection="ids"
open="("
close=")"
separator=","
item="item">
#{item}
</foreach>
</delete>
说明:当在foreach 属性直接使用ids接收dao中参数数据时,dao接口方法应
使用@Param注解进行声明.假如没有使用注解声明可以使用 array接收到方法中参数数组数据
step03:编写单元测试
@Test
public void testDeleteObjectByIds(){
//1.创建session
SqlSession session=factory.openSession();
//2.执行sql
//2.1获取dao对象
BlogDao dao=session.getMapper(BlogDao.class);
int rows=
dao.deleteObjectByIds(new String[]{"9"});
System.out.println("delete.rows="+rows);
session.commit();
//3.关闭session
session.close();
}
课后尝试:
完成一个批量插入的操作?
4.6. 应用扩展?
课后了解:(本阶段不作为重点掌握)
1) MyBatis 中拦截器?
2) MyBatis 中分页插件应用?
3) MyBatis 中通用mapper的实现?
3) Mybatis 中逆向工程的生成?
4) ……..
5. 总结
5.1. 重点和难点分析
Day01:
1. MyBatis 是什么,要解决什么问题?
2. MyBatis 底层架构体系?
3. MyBatis 核心组件以及API?
4. MyBatis 编程的基本步骤?
5. MyBatis 数据操作执行流程?
6. ...
Day02:
1. MyBatis 框架编程中CRUD操作的基本实现?
1) Session.selectOne(….),session.selectList(…)
2) Session.insert(“命名空间+元素id”),….
3) Session.update(…),….
4) Session.delete(….),…..
2. MyBatis 框架编程中的事务处理(自动提交事务,手动提交事务)?
1) factory.openSession(true),自动提交
2) factory.openSession();默认false,为手动提交
3. MyBatis 框架编程中基于DAO接口的CRUD实现方式?
1) 定义DAO接口
2) 定义Mapper映射文件
3) Mapper文件与DAO接口之间的映射(命名空间,元素id,参数)
4. MyBatis 框架编程中DAO接口与Mapper映射的绑定规则?
1) DAO的类全名与mapper的命名空间相同
2) DAO类的方法名与mapper文件的元素id相同
3) DAO类中的方法参数与mapper文件中参数获取对应
5. MyBatis 框架编程中数据库连接信息的配置及获取方式?
1) UNPOOLED(不使用连接池):了解,每次访问数据库都会创建一个新的连接。
2) POOLED (内置连接池):重点掌握。可以实现Connection对象的复用
3) JNDI(使用命名空间目录接口服务):了解
6. MyBatis 框架编程中映射文件内部的对象别名配置?
1) 配置文件中通过typeAliases元素进行配置
2) 应用于映射文件中参数类型(parameterType),结果类型(resultType)
Day03.MyBatis增强
业务增强
1. 动态排序
2. ID生成策略
高级应用
1. 日志配置
2. 缓存的配置及应用
3. 高级映射的基本实现(many2one,……)
4. 延迟加载的实现
5. 动态SQL的基本应用(foreach,………)
6. ……………
思考:mybatis应用过程中你都考虑到了哪些方面的优化?
1) 映射优化(实体类的属性要提供get/set方法,不提供可以映射吗?)
2) 缓存优化(使用缓存时要考虑缓存失效,脏读,对象拷贝,序列化,。。)
3) SQL注入优化($的应用)
4) ID策略优化(自增长)
5) 延迟加载优化(按需加载)
6) 动态SQL优化 (空格问题,..)
7) ……..
5.2. 常见FAQ
1. MyBatis 底层数据方法需要JDBC吗?
2. MyBatis 核心配置文件中有哪些常用配置?
3. MyBatis 映射文件中常用元素定义有哪些?
4. MyBatis 数据操作常用实现方式有哪些?
5. MyBatis 映射文件元素如何获取参数数据?
6. MyBatis 映射文件元素中获取参数数据的常用方案.