MYBATIS框架

1. MyBatis 简介

1.1. MyBatis 概述?

1.1.1. MyBatis 是什么?

MyBatis apacheibatis演变而来,可以从如下几个方面加强理解与应用。

 

1) 软件框架(Framework): 用于解决软件中的通用型(共性)问题

2) 持久层框架(数据访问层):用于更好解决数据持久化问题

3) 半自动的ORM框架解决对象关系映射问题。

 

思考

1) 如何理解ORM对象关系映射):实现对象以及对象与对象关系的映射。

 

课后了解: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也不例外,它会具体如下几个结构.

 

思考:谈谈你对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映射):ORM

 

1.3.2. MyBatis 核心API

MyBatis 项目中核心API

 

  1. SqlSessionFactoryBuilder (负责读取配置文件,创建SqlSessionFactory对象)
  2. SqlSessionFactory(负责创建SqlSession对象)
  3. SqlSession(负责连接的维护,事务的处理,类似JDBC中的Connection)
  4. .....

 

思考:

 

1)核心组件在应用的一个角色定位?

2)应用底层会用到哪些设计模式?建造模式,工厂模式

 

了解job.alibaba.com

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:添加mybatis依赖(pom.xml)以及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.cnmaven配置

 

添加mybatis依赖

  org.mybatis

  mybatis

  3.2.8

 

 

添加MySQL驱动依赖

   mysql

   mysql-connector-java

   5.1.40

 

 

添加junit依赖

   junit

   junit

   4.12

 

 

思考:

1) 添加依赖时注意groupId的选择?(选正规的)

2) 添加依赖以后pom.xml文件假如有错什么原因?

检测网络,检测maven配置(setting.xml)

 

2.2.3. 添加配置及映射文件

src/main/resources目录下创建配置文件mybatis-configs.xml,内容如下:

 

"1.0" encoding="UTF-8"?>

  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

  "http://mybatis.org/dtd/mybatis-3-config.dtd">

  

  "development">

    "development">

      "JDBC"/>

      "POOLED">

        "driver" value="com.mysql.jdbc.Driver"/>

        "url"  value="jdbc:mysql:///cgb1711"/>

        "username" value="root"/>

        "password" value="root"/>

      

    

  

  

  

     "mapper/BlogMapper.xml"/>

  

 

说明:

1)配置文件的头官方文档进行拷贝.

2)配置文件元素没有提示什么原因? 对应dtd文件取不到

提示信息的配置请参考:http://schema.tedu.cn/proxy/

 

step02:src/main/resources/mapper目录下创建映射文件BlogMapper.xml

"1.0" encoding="UTF-8"?>

  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

"com.jt.blog.BlogDao">

 

说明:

1) 映射文件的命名空间用于约束元素id的唯一性.

2) 映射文件的格式最好包结构形式

3) 映射文件内部可以定义很多元素,每个元素必须有一个唯一id,例如select

 

在mapper文件中添加如下元素:

 

查询所有blog数据

"findBlogs" resultType="map">

         select * from blog

 

根据id查询blog数据

"findBlogById" resultType="map">

         select *

         from blog 

         where id=#{id}

 

限制查询blog元素(此方式参数的传递应用Object[]数组进行封装)

"findPageBlogs"

            resultType="map">

          select *

          from blog

          limit #{array[0]},#{array[1]}

 

限制查询Blog元素(此方式的参数应用map进行封装,#{}表达式中的内容为map中的key

 

"findPageBlogs"

            resultType="map">

          select *

          from blog

          limit #{startIndex},#{pageSize}

 

表中插入数据元素定义

方案1:(测试时参数数据可以为Object[]类型数组)

   "insertObject">

          insert into blog

          (id,title,content,createdTime)

          values

          (null,#{array[0]},#{array[1]},now())

    

 

方案2:(测试时参数数据可以为Blog类型对象或Map对象)

   "insertObject">

          insert into blog

          (id,title,content,createdTime)

          values

          (null,#{title},#{content},now())

    

 

 

 

修改表中数据元素定义

方案1:(测试时参数传递需要为Object[]类型的数组)

"updateObject">

         update blog 

         set title=#{array[0]},

             content=#{array[1]}

         where id=#{array[2]}

 

方案(测试时参数传递可以是map或者blog对象)

 

"updateObject">

         update blog 

         set title=#{title},

             content=#{content}

         where id=#{id}

 

 

 

删除表中元素元素定义

 

  "deleteObject">

         delete

         from blog

         where id=#{id}

    

 

 

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 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 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> list=

session.selectList(statement, parameter);

for(Mapmap: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) 概要流程分析:(了解)

 

 

 

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桌面项目添加依赖

 

 

 

  org.mybatis

  mybatis

  3.2.8

 

 

 

 

   mysql

   mysql-connector-java

   5.1.40

 

 

  

 

   junit

   junit

   4.12

 

 

 

 

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文件读取.

 

"1.0" encoding="UTF-8"?>

  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

  "http://mybatis.org/dtd/mybatis-3-config.dtd">

  

  "config.properties"/>

  

  "development">

    "development">

      "JDBC"/>

      "POOLED">

        "driver" value="${driver}"/>

        "url"  value="${url}"/>

        "username" value="${username}"/>

        "password" value="${password}"/>

      

    

  

 

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 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映射文件

"1.0" encoding="UTF-8"?>

  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

"com.jt.dao.BlogDao">

 

 

 

BlogMapper文件中添加与BlogDao接口对应的映射元素

 

添加基于ID进行查询的元素

"findBlogById"

           parameterType="int"

           resultType="blog">

           select *

           from blog

           where id=#{id}

   

 

添加分页查询元素

 

  "findPageBlogs"

           resultType="blog">

           select *

           from blog

           limit #{offset},#{pageSize}

   

 

添加insert元素

   "insertObject"

           parameterType="blog">

           insert into blog

           (id,title,content,createdTime)

           values

           (null,#{title},#{content},now())

   

 

添加更新元素

   "updateObject"

           parameterType="blog">

           update blog

           set title=#{title},content=#{content}

           where id=#{id}

   

 

添加删除元素

"deleteObject"

           parameterType="int">

           delete from blog where id=#{id}

 

在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 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 findBlogs(

 @Param("column") String column,

@Param("seq") String seq);

 

映射文件中的实现

 

"findBlogs" 

           resultType="blog">

         select *

         from blog

         order by ${column} ${seq}

 

思考:

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) 权限系统

保存用户信息,获取用户信息在数据库中的主键?还有保存用户和权限关系数据

 

   解决方案: 当对象对应的表中的记录为自增长时,可以采用如下方案

 

      "insertObject"

           parameterType="blog"

           useGeneratedKeys="true"

           keyProperty="id">

           insert into blog

           (id,title,content,createdTime)

           values

           (null,#{title},#{content},now())

   

   

其中:keyProperty属性用于指定参数中的id属性.

 

  1. 多线程并发的向表中写入数据时,假如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生成.映射文件参考

"insertObject" parameterType="author">

      "id" 

                  resultType="string"

                  order="BEFORE">

          select replace(uuid(),'-','')

      

       insert into author

       (id,username,password,email)

       values

       (#{id},#{username},#{password},#{email})

 

  Step05 编写单元测试

 

说明:

4) 此方式的实现依赖于数据库,存在一定局限性,例如当前这种写法仅适用于mysql

5) 假如不想对数据库有较大依赖,可在应用程序中,通过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依赖(一代)

 

  

log4j

log4j

1.2.17

  

 

  1. 添加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

 

  1. 设置mybatis的日志实现(mybatis-configs.xml)

 

  

     "logImpl" value="log4j"/>

  

 

其中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("[email protected]");

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("[email protected]");

dao.updateAuthor(a1);

Author a2=dao.findAuthorById(id);

System.out.println(a2);

session.commit();

session.close();

}

 

验证缓存脏读问题(此问题可通过配置缓存类型为STATEMENT类型解决)

 

@Test

public void testFirstLevelCache03(){

SqlSession session1=2factory.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("[email protected]");

int rows=dao2.updateAuthor(a);

session2.close();

Author a2=dao1.findAuthorById(id);

System.out.println(a2);

session1.close();

}

 

 

 

MyBatis:一级缓存应用说明:

 

1) 默认是开启的(也是应用最多的一种)

2) 其类型为SESSIONSTATEMENT两种,默认是SESSION类型

a) 缓存对象的生命周期不同(例如session类型的一级缓存,session关闭就失效.)

b) 其类型的配置可在配置文件中通过这个localCacheScope属性进行配置。

3) 一级缓存在每次更新(同一个session)后都会失效。

4) 一级缓存在事务并发执行时可能会出现脏读,但相对于STATEMENT效率会高一些。

 

MyBatis 二级缓存应用:

 

MyBatis 二级缓存默认是没有开启的,需要在映射文件中加上元素

MyBatis 二级缓存应用步骤:

Step01: 修改mybatis核心配置文件,添加缓存设置.

 

     "cacheEnabled" value="true"/>

  

 

Step02: 在映射文件XxxMapper.xml)中配置Cache策略.

 

 

     eviction="LRU"

     flushInterval="60000"

     size="512"

     readOnly="true"/>

 

这个表示创建了一个 LRU缓存,最多存储512对象,并每隔 60 秒刷新,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。其中:

 

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说明:

 

1)readOnly的值为true,缓存中保存的是堆内存中对象的引用.每次缓存数据都是获得的同一个对象.readOnlyfalse,首先会将查询到的对象,拷贝到缓存一份(对象需要实现序列化接口),然后从缓存取数据每次都是执行对象的拷贝.

 

2)MyBatis 中的二级缓存同样存在脏读问题尤其是在分布式应用场景中表现出的问题就会更加突出

 

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 findBlogAuthorById(Integer id);

 

Step02: BlogMapper中映射元素定义:

 

   "findBlogAuthorById"

           resultType="map">

          select b.*,a.username,a.email

          from blog b join author a

          on b.authorId=a.id

          where b.id=#{id}        

   

 

优势:

1)映射文件中的代码量相对较小,存储简单。

缺陷:

1) 可读性相对较差(map中究竟存储哪些数据,需要通过输出才知道)

2) 从空间使用上可能会有一定的资源浪费

3) 只要有表关联,性能方面就会多少的影响。

4) 分布式应用中不建议直接的表关联

 

方案:

 

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中映射元素定义

 

   "findBlogResultById"

           resultMap="blogResult">

          select b.*,a.username,a.email

          from blog b join author a

          on b.authorId=a.id

          where b.id=#{id}        

   

 

"blogResult" type="com.jt.vo.BlogResult">

     "id" column="id"/>

     "title" column="title"/>

     "content" column="content"/>

     "createdTime" column="createdTime"/>

     

     "author" javaType="author">

        "id" column="authorId"/>

        "username" column="username"/>

        "email" column="email"/>

     

   

 

优势:

空间利用率有可能会有所提升,对象可读性有一定成都的提升。

缺陷:

映射文件中的代码量相对较大,映射相对复杂,可读性不太好。

 

Step04: 单元测试

 

 

 

方案:(通过两次查询实现):作业

 

Step01: 接口(BlogDao)中方法定义

 

BlogResult findBlogResultWithId(Integer id);

 

Step02: 映射文件BlogMapper)元素定义

 

"com.jt.vo.BlogResult" id="blogResultMap">

     "author" 

                    column="authorId"

                    javaType="author"

                    select="com.jt.dao.AuthorDao.findObjectById">

      

 

 "findBlogResultWithId" resultMap="blogResultMap">

       select * from blog where id=#{id}        

 

 

在这个元素映射时假如表中的字段名与类中的属性名相同,可以不使用id,result元素定义具体映射。

此形式的映射关系定义底层会发起两次查询获取blog以及它关联的author数据信息。

 

Step03: 基于DAO接口方法进行单元测试

 

方案优势:

1) 映射文件中代码量小了,可维护性提高了。

2) 可通过配置实现延迟加载(在延迟加载小节会讲到),让查询更加灵活。

方案劣势

1) 一个结果需要多次查询,假如所有数据需要立刻加载,性能会相对较低。

 

4.4. 延迟加载应用

4.4.1. 延迟加载基本概述

延迟加载是按需加载的一种实现策略,一般应用与关联查询中,针对关联对象数据进行延迟加载(何时需要何时加载).以提高内存的使用效率

4.4.2. 延迟加载基本实现

 

Step01: 添加依赖(CGLIB依赖,底层延迟加载,构建代理对象时需要)

 

  cglib

  cglib

  3.2.5

 

 

Step02: 配置全局延迟加载(当有些查询不需要延迟加载,可以在查询对应属性中使用fetchType进行覆盖性设置,例如lazy表示延迟加载,eager表示实时加载)

 

     ....

     

     "lazyLoadingEnabled" value="true"/>

     

     "aggressiveLazyLoading" value="false"/>

  

 

Step03: 关联映射中就可以直接使用延迟加载了.例如

 

"com.jt.vo.BlogResult"

              id="blogResultMap">

       "author" 

                    column="authorId"

                    javaType="author"

        select="com.jt.dao.AuthorDao.findObjectById">

      

   

   

   "findBlogResultWithId"

           resultMap="blogResultMap">

       select * from blog where id=#{id}        

   

 

在测试类中只访问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文件中定义删除元素

 

"deleteObjectByIds"

           parameterType="string">

         delete from blog

         where id in  

         

         "ids"

                  open="("

                  close=")"

                  separator=","

                  item="item">

                  #{item}

         

 

 

说明:在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();

}

转载于:https://www.cnblogs.com/zyjssm/p/9432657.html

你可能感兴趣的:(MYBATIS框架)