Mybatis快速入门

Mybatis

1. Mybatis简介

1.1 网址

中文文档:MyBatis中文网](https://mybatis.net.cn/)(非常有用,所有的知识)

jar包

MyBatis · GitHub

Maven Repository: org.mybatis » mybatis (mvnrepository.com)

1.2 百度百科

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件。易于学习,易于使用。通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的ORM字段关系映射。
  • 提供对象关系映射标签,支持对象关系组建维护。
  • 提供xml标签,支持编写动态sql。

Dao Service Controller

2. 第一个Mybatis程序

思路: 搭建环境,导入Mybatis,编写代码,测试

2.1 环境

mysql

mybatis

junit单元测试

2.2 导入Mybatis(mybatis-config.xml核心配置文件+工具类)

这一部分都是死操作

mybatis-config.xml核心配置文件

<?xml version="1.0" encoding="UTF8" ?>
<!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://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!--    每一个Mapper.xml都需要在核心文件这里注册-->
    <mappers>
        <mapper resource="com/cao/dao/UserMapper.xml"/>
        <!--     或者这样写<mapper class="com.cao.dao.UserMap"></mapper>   -->
    </mappers>
</configuration>

environment, mapper等等之后介绍

mybatis工具类

public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;

    static{
        try {
            //这是定死的代码,不用动
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //获取sqlSession,相当于jdbc的connection
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }

}

每次获取完sqlSession后记得关闭资源close

2.3 编写~~mapper.xml(最重要)

1.实体类(entity)

2.Dao接口(Mapper接口)

3.xml

整个UserMapper.xml相当于UserDaoImpli

namespace就是要实现的接口名

id就是要实现的方法名

<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 相当于UserMapper接口-->
<mapper namespace="com.cao.dao.UserMapper">
    <select id="getUserList" resultType="com.cao.pojo.User">
        select * from mybatis.user
    </select>
</mapper>

2.4 测试

@Test
public void test(){
    //第一步,获得SQLSession对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();//死操作
    //执行SQL
    //方式一
    UserDao userDao = sqlSession.getMapper(UserDao.class);//获得接口类
    List<User> users = userDao.getUser();

    //方式二
    //List<User> users = sqlSession.selectList("cn.bloghut.dao.UserDao.getUser");//不用

    for (User user : users){
        System.out.println(user);
    }
    //关闭SQLSession
    sqlSession.close();//死操作
}

sqlSession.getMapper(UserDao.class) 获得接口,直接通过接口调用方法

sqlSession.selectList(“cn.bloghut.dao.UserDao.getUser”) 获得方法,不用这种方法,复用性不高

唯一要改变的就是调用方法的过程

2.5 万能的Map

假设我们的实体类参数过多,我们应当考虑Map。用处:想传什么传什么

这是一种非常简洁的传参方式能够解决很多问题,但不是非常正规。

~~Mapper

int insert(Map map);

~~Mapper.xml

<insert id="insert" parameterType="map">
        insert into `user` values (#{id},#{name},#{pwd});
</insert>

注意与

<insert id="insertUser" parameterType="com.cao.pojo.User">
        insert into `user` values(#{id},#{name},#{pwd});//这里的id,name,pwd都得是实体类中的对象缺少自由性
</insert>

~~test/service

		HashMap<String, Object> map = new HashMap<>();
        map.put("id",5);
        map.put("name","曹");
        map.put("pwd","123");
        userMap.insert(map);
        sqlSession.commit();

即可

3. 配置解析

3.1 核心配置文件mybatis-config.xml

3.2 环境

1.MyBatis 可以配置成适应多种环境

这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。

尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

通过id和default转换

2.事务管理器(transactionManager)

在 MyBatis 中有两种类型的事务管理器(也就是 type=“[JDBC|MANAGED]”)

3.数据源(dataSource)

有三种内建的数据源类型(也就是 type=“[UNPOOLED|POOLED|JNDI]”)

3.3 属性

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。db.properties

<properties resource="db.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>

也可以在properties里面设置

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

但是有优先级

如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:

  • 首先读取在 properties 元素体内指定的属性。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。

3.4 别名typeAliases

1.DIY别名,只可以一个类一个类设置,一般是实体类

<typeAliases>
        <typeAlias type="com.cao.pojo.User" alias="User"/>
</typeAliases>

2.还可以对一个包进行设置

    <typeAliases>
        <package name="com.cao.pojo"/>
    </typeAliases>

这样默认别名为首字母小写

但还是可以通过注解改类的名字进行设置

@Alias("author")
public class Author {
    ...
}

3.5 设置

没什么重要的

3.6 映射器

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

建议使用xml映射 否则其他的方式接口和xml文件同名同包

3.7 生命周期和作用域

理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题

SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式

SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。

SqlSessionFactory就是数据库连接池

SqlSession就是一次请求

Mapper就是一次请求下的映射,一次请求可以有多个Mapper

4. 属性名和字段名不一致(重难点)

4.1 问题引出

字段名

属性名

这样就会出现匹配问题

解决方法:查询时候使用别名,最佳:resultMap

4.2 简单的resultMap

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yyP78dLL-1669283950211)(C:\Users\14876\Desktop\md\SSM\m6.png)]

<resultMap id="getUser" type="user">
    <result column="pwd" property="password"/>
</resultMap>

<select id="getUserList" resultMap="getUser">
    select * from mybatis.user;
</select>
  • resultMap 元素是 MyBatis 中最重要最强大的元素。 ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
  • resultType:MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名(这是一个基本的 SQL 特性)来完成匹配
  • 如果这个世界总是这么简单就好了。

更复杂的问题见9

5. 日志

5.1 日志工厂

以前使用sout,debug

现在日志工厂logImpl帮助我们排错!!!

SLF4J

LOG4J 掌握

LOG4J2

JDK_LOGGING

COMMONS_LOGGING

STDOUT_LOGGING 掌握

NO_LOGGING

需要在配置文件配置

5.2 标准日志工厂的实现:

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

5.3 LOG4J日志工厂

导入maven依赖:

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

编写资源文件log4j.properties

### 配置根 ###
log4j.rootLogger = debug,console ,fileAppender,dailyRollingFile,ROLLING_FILE,MAIL,DATABASE

### 设置输出sql的级别,其中logger后面的内容全部为jar包中所包含的包名 ###
log4j.logger.org.apache=debug
log4j.logger.java.sql.Connection=debug
log4j.logger.java.sql.Statement=debug
log4j.logger.java.sql.PreparedStatement=debug
log4j.logger.java.sql.ResultSet=debug

### 配置输出到控制台 ###
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern =  %d{ABSOLUTE} %5p %c{1}:%L - %m%n

### 配置输出到文件 ###
log4j.appender.fileAppender = org.apache.log4j.FileAppender
log4j.appender.fileAppender.File = logs/log.log
log4j.appender.fileAppender.Append = true
log4j.appender.fileAppender.Threshold = DEBUG
log4j.appender.fileAppender.layout = org.apache.log4j.PatternLayout
log4j.appender.fileAppender.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

### 配置输出到文件,并且每天都创建一个文件 ###
log4j.appender.dailyRollingFile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyRollingFile.File = logs/log.log
log4j.appender.dailyRollingFile.Append = true
log4j.appender.dailyRollingFile.Threshold = DEBUG
log4j.appender.dailyRollingFile.layout = org.apache.log4j.PatternLayout
log4j.appender.dailyRollingFile.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

### 配置输出到文件,且大小到达指定尺寸的时候产生一个新的文件 ###
log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender 
log4j.appender.ROLLING_FILE.Threshold=ERROR 
log4j.appender.ROLLING_FILE.File=rolling.log 
log4j.appender.ROLLING_FILE.Append=true 
log4j.appender.ROLLING_FILE.MaxFileSize=10KB 
log4j.appender.ROLLING_FILE.MaxBackupIndex=1 
log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout 
log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n

4j.appender.A1.layout=org.apache.log4j.xml.XMLLayout

mybatis核心配置,设置log4j为日志实现

<!--设置日志-->
<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>

简单使用:

1.在要使用log4j的类中导入Apache的包

import org.apache.log4j.Logger;

2.日志对象,参数为当前类的class

static Logger logger = Logger.getLogger(UserTest.class);

3.使用

//日志级别
//info 常用
//debug
//error
@Test
public void testLog4j(){
    logger.info("info:进入了testLog4j方法");
    logger.debug("debug:进入了testLog4j方法");
    logger.error("error:进入了testLog4j方法");
}

6. 分页实现

专业的事交给专业的人办,不要一下全查询出来再添加条件,这样浪费资源

最重要的实现是limit+万能的Map

6.1 limit实现(原理)

//在Mapper interface中:
//分页
List<User> getUserList2(Map map);
<!--在mapper.xml中-->
<select id="getUserList2" parameterType="map" resultMap="getUser">
    select * from `user` limit #{startIndex},#{pageSize};
</select>
//test中
public void test(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper userMap = (UserMapper) sqlSession.getMapper(UserMapper.class);

    HashMap<String, Object> map = new HashMap<>();
    map.put("startIndex",1);
    map.put("pageSize",2);

    List<User> userList = userMap.getUserList2(map);

    sqlSession.close();

    for (User user:userList) {
        System.out.println(user);
    }
}

6.2 RowBounds类实现

写查询代码不用使用limit,但最终原理还是limit

List<User> getUserByRowBounds();
<select id="getUserByRowBounds" resultMap="UserMap">
    select * from users 
</select>
@Test
public void t5(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    RowBounds rowBounds = new RowBounds(0, 2);
    //通过Java代码层实现分页
    List<User> users = sqlSession.selectList("cn.bloghut.dao.UserMapper.getUserByRowBounds", null, rowBounds);//selectList方法已经不建议使用
    for (User user : users) {
        System.out.println(user);
    }
    sqlSession.close();
}

6.3 分页插件PageHelper

MyBatis 分页插件 PageHelper

7. 使用注解开发

7.1 面向接口开发

大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程。

根本原因:解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好

在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;

各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

关于接口的理解
  接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离
  接口的本身反映了系统设计人员对系统的抽象理解。

接口应有两类:
    第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
    第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface) ;
一个体有可能有多个抽象面。抽象体与抽象面是有区别的。

三个面向区别
  面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法.
  面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现.
  接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题.更多的体现就是对系统整体的架构

7.2 使用注解

对于像 BlogMapper 这样的映射器类来说,还有另一种方法来完成语句映射。 它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置。比如,上面的 XML 示例可以被替换成如下的配置:

package org.mybatis.example;
public interface BlogMapper {
  @Select("SELECT * FROM blog WHERE id = #{id}")
  Blog selectBlog(int id);
}

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句

选择何种方式来配置映射,以及认为是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换

7.3 CRUD注解

简单的select

UserMapper中:

@Select("select * from user;")
List<User> getAllUsers();

Test中:

public void test(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> users = mapper.getAllUsers();
    for (User user : users) {
        System.out.println(user);
    }

    sqlSession.close();
}

注意还需要把mapper改掉必须使用绑定接口

<mappers>
    <!--<mapper resource="com/cao/map/UserMapper.xml"/>-->
    <mapper class="com.cao.map.UserMapper"/>
</mappers>

多参数 CRUD

必须要加上@Param注解(非常重要)

  • 基本类型的参数或者String类型,需要加上
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以忽略
  • 在SQL里面使用的就是我们绑定的属性名

#{} 和 ${} 区别

@Select("select * from user where id=#{id} and name=#{name}")
User getUser(@Param("id") int id,@Param("name") String name);

注意自动提交事务可以在工具类那儿openSession传入参数

8. Lombok使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J3WPcfSr-1669283950213)(C:\Users\14876\Desktop\md\SSM\m11.png)]

步骤:

首先在idea中安装lombok插件(2020年之后idea自动集成lombok,不用下载插件了)

在项目中导入包

很简单

9. 一对多,多对一,处理(resultMap)

9.1 外键关系在实体类的具象化

视情况而定

一对多和多对一只是角度不同,但本质是相同的

例如,学生和老师,多个学生有一个老师,一个老师有多个学生。

create table teacher(
    id int(10) not null auto_increment ,
    name varchar(30) default null,
    primary key(id)
)engine=innodb default charset=utf8;

insert into teacher(id,name) values(1,'秦老师');

-- 再创建一个student表
create table student(
  id int(10) not null auto_increment,
  name varchar(30) default null,
  tid int(10) default null,
  primary key(id),
  key fktid (tid),
  constraint fktid foreign key (tid) references teacher(id)

)engine=innodb default charset=utf8;


insert into student(id,name,tid)values (1,'小明','1');
insert into student(id,name,tid)values (2,'小红','1');
insert into student(id,name,tid)values (3,'小张','1');
insert into student(id,name,tid)values (4,'小李','1');
insert into student(id,name,tid)values (5,'小王','1');

Student

@Data
public class Student {
    //表中元素
    private int id;
    private String name;
    private int tid;
    
    //多个学生有一个老师,这样可以容易用来多表查询
    private Teacher teacher;
}

对象

Teacher

@Data
public class Teacher {
    private int id;
    private String name;

    //一个老师有多个学生,这样也更加容易多表查询
    private List<Student> students;
}

集合

tips: pay attention to private Teacher teacher; private List students;

9.2 多对一处理

从学生的角度,多对一,查询每个学生信息附带老师(对象)的信息

这种问题就是俩种处理方式:

  • 子查询
  • 联表查询

最简单的就是联表查询,这也是推荐写法

联表查询

StudentMapper

public interface StudentMapper {
    @Select("select * from `student`;")
    List<Student> getAllStudents();

    //查询所有学生的信息加上老师信息
    List<Student> getInfomation();
}

StudentMapper.xml

<select id="getInfomation" resultMap="studentTeacher">
    select s.id sid,s.name sname, t.id tid, t.name tname from `student` as s, `teacher` as t
    where s.tid=t.id;
</select>

<resultMap id="studentTeacher" type="student">
    <id column="sid" property="id"></id>
    <result column="sname" property="name"></result>
    <result column="tid" property="tid"></result>
    <association property="teacher" javaType="teacher">
        <result column="tid" property="id"></result>
        <result column="tname" property="name"></result>
    </association>
</resultMap>

这里使用的是association对应的是对象javaType

子查询
<!--自定义返回值-->
<resultMap id="StudentTeacher" type="Student">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <!--复杂的属性,我们需要单独处理
        对象:association
        集合:collection
    -->
    <association property="teacher" column="tid" javaType="Teacher" select="getTeacherById"/>
</resultMap>

<!--查询所有学生-->
<select id="getStudents" resultMap="StudentTeacher">
    select * from student
</select>

<!--根据教师id查询教师信息-->
<select id="getTeacherById" resultType="Teacher">
    select * from teacher where id = #{id}
</select>

9.3 一对多处理

联表查询

TeacherMapper

public interface TeacherMapper {
    //获得一个教师的所有学生信息
    List<Teacher> getTeacherInfo();
}

TeacherMapper.xml

<select id="getTeacherInfo" resultMap="teacherStudent">
        select t.id tid, t.name tname, s.id sid,s.name sname from `teacher` as t, `student` as s
            where t.id=s.tid;
    </select>

    <resultMap id="teacherStudent" type="teacher">
        <id property="id" column="tid"></id>
        <result property="name" column="tname"></result>
        <collection property="students" javaType="list" ofType="student">
            <result property="id" column="sid"></result>
            <result property="name" column="sname"></result>
            <result property="tid" column="tid"></result>
        </collection>
    </resultMap>

这里使用的是collection,对应的是集合,ofType泛型的约束类型,javaType泛型

子查询
<select id="getTeacherById2" resultMap="TeacherStudent2" parameterType="int">
    select * from teacher where id = #{id}
</select>

<resultMap id="TeacherStudent2" type="Teacher">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <collection property="students" javaType="ArrayList" ofType="Student" select="cn.bloghut.dao.StudentMapper.getStudentsByTid" column="id"/>
</resultMap>

<!--这个方法是在Student mapper文件中的-->
<select id="getStudentsByTid"  resultType="Student">
    select * from student where tid = #{id}
</select>

9.4 小结

关联-association
  集合-collection
  JavaType & ofType
    JavaType 用来指定实体类中的类型的
    ofType 用来指定映射到List 集合中的pojo类型 ,泛型中的约束类型

10. 动态SQL-对比字符串拼接

**动态 SQL 是 MyBatis 的强大特性之一。**如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

用万能的map配合可以实现很多功能

10.1 if 重点

<select id="queryBlogIF" resultType="Blog" parameterType="map">
    select * from blog where 1 = 1
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</select>

就是用来判断是否传入参数,传入了则拼接,没传入则不拼接

10.2 trim、where、set 重点

这些可以解决拼接sql非常麻烦的问题例如and,or,‘,’等

<select id="queryBlogWhere" resultType="Blog" parameterType="map">
    select * from blog
    <where>
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </where>
</select>

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

用于动态更新语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

10.3 choose (when, otherwise)

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

10.4 foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

10.5 小结

动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就可以了

11. 缓存

11.1 缓存介绍

查询 : 连接数据库 :好资源!
一次查询的结果,给他暂存到一个可以直接取到的地方!—》内存 : 缓存

我们再次查询相同数据的时候,直接走缓存,就不用走数据库了

1.什么是缓存(Cache)?

存在内存中的临时数据
将用户经常查询的数据放在缓存(内存),用户去查询数据就不用从磁盘上的(关系型数据库数据文件)查询,从缓存中查询,从而提高程序效率,解决了高并发的性能问题。

2.为什么使用缓存?

减少和数据库的交互次数,减少系统开销,提高系统效率

3.什么样的数据能使用缓存?

经常查询且不改变的数据。
缓存的重要性是不言而喻的。 使用缓存, 我们可以避免频繁的与数据库进行交互, 尤其是在查询越多、缓存命中率越高的情况下, 使用缓存对性能的提高更明显。

11.2 mybatis缓存

MyBatis 包含一个非常强大的查询缓存特性,它可以非 常方便地配置和定制。缓存可以极大的提升查询效率。

  • MyBatis系统中默认定义了两级缓存。 一级缓存和二级缓存。
  • 默认情况下,只有一级缓存(SqlSession级别的缓存, 也称为本地缓存)开启。
  • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存
  • 为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存,将缓存数据保存到三方缓存里。

11.3 一级缓存(本地)

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中
  • 以后如果需要获取相同的数据,直接从缓存中取,没必要再去查询数据库。
  • 一级缓存是sqlSession级别的缓存,默认一直是开启的。

测试步骤:

  • 开启日志
  • 测试在一个Session中查询两台相同记录
  • 查看日志输出

缓存失效情况

  • 查询不同的东西
  • 增删改操作,可能会改变原来的数据,所以必定会刷新缓存
  • sqlSession.clearCache();//手动清理缓存

小结:一级缓存默认是开启的,只在一次SQLSession中有效,也就是拿到连接 到关闭连接这个区间有效。

11.4 二级缓存(全局)

基于namespace级别的缓存,一个namespace对应一个二级缓存。

工作机制:

  1. 一个会话,查询一条数据,这个数据就会被放在当前的一级缓存中。
  2. 如果会话关闭一级缓存中的数据会被保存到二级缓存中,新的会话查询信息,就可以参照二级缓存。
  3. sqlSession即通过EmployeeMapper查询Employee,也通过DepartmentMapper查询Department,不同namespace查出的数据会放在自己对应的缓存中(map)。

核心配置xml

<!--显示的开启全局缓存-->
<setting name="cacheEnabled" value="true"/>

Mapper.xml

<!--在当前Mapper.xml中使用二级缓存-->
<cache/>

测试
我们需要将实体类序列化!否则就会报错!

小结:

  1. 只要开启了二级缓存,在同一个Mapper下就有效
  2. 所有的数据都会先放在一级缓存中;
  3. 只有当会话提交,或者会话关闭的时候,才会提交到二级缓存中

11.5 缓存原理

img

11.6 自定义缓存-ehcache

Ehcache是一种广泛使用的开源Java分布式缓存。

  1. maven依赖
<!--ehcache-->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.1.0</version>
</dependency>
  1. mapper中设置缓存
  2. ehcache配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--
       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir  – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径
     -->
    <diskStore path="java.io.tmpdir/Tmp_EhCache"/>
    <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      overflowToDisk:是否保存到磁盘,当系统当机时
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>
  
    <cache
            name="cloud_user"
            eternal="false"
            maxElementsInMemory="5000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="1800"
            memoryStoreEvictionPolicy="LRU"/>
  
</ehcache>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值