Mybatis

一、简介

MyBatis 是一款优秀的 持久层 框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

持久层:

  • 持久化是将程序的数据在持久状态和瞬时状态转化的过程。比如内存是断电即失的,而数据库(JDBC)、io文件可以实现数据的持久化
  • 持久层是为了完成持久化工作的代码块

为什么使用Mybatis:

  • 提供映射标签,支持对象与数据库的orm字段关系映射
  • 提供对象关系映射标签,支持对象关系组件维护
  • 提供xml标签,支持编写动态sql

现在在GitHub中下载:https://github.com/mybatis/mybatis-3。点击Releases下载最新版本

二、Hello Mybatis

  1. 创建数据库(Navicat)
  2. 新建项目

(1)新建一个普通的maven项目
(2)删除src目录
(3)导包:mysql驱动、mybatis、junit等

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.17</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.10</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
  1. 创建一个模块

详见mybatis3官网入门教程

(1)在上面的项目中新建一个module

核心配置文件

(2)编写mybatis的核心配置文件
在resources目录下,新建一个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="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

$ {driver}改成mysql驱动,$ {url}改成数据库的url,$ {username}和$ {password}改成MySQL的用户名和密码,例如:

                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123321"/>

注意,xml中url的“&”要写成转义符“&”

工具类

(3)编写mybatis工具类
SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。

package com.y.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.*;

public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory ;
    static {
        //使用mybatis获取SqlSessionFactory对象
        try {
            String resource = "mybatis-config.xml";//读取resources目录下的文件时url可以直接为文件名
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //用SqlSessionFactory获得 SqlSession 的实例
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}
  1. 编写代码
  • 实体类
package com.y.pojo;

import lombok.Data;
@Data
public class User {
    private int id;
    private String name;
    private String pwd;
}

接口

package com.y.dao;

import com.y.pojo.User;
import java.util.List;

public interface UserDao {
    List<User> getUserList();
}

Dao 接口的工作原理是什么?
通常一个 xml 映射文件(Mapper配置文件)对应实现一个 Dao 接口。接口的全限名,就是Mapper配置文件中的 namespace 的值;接口的方法名,就是映射文件中的 id 的值;接口方法内的参数,就是传递给 sql 的参数。

Mapper配置文件(接口实现类)

由原来的UserDaoImpl类转变为一个Mapper配置文件
(1)写Mapper配置文件: 在Dao接口的packet中新建一个xml,这里命名为UserMapper.xml,以查询语句为例

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace会绑定一个对应的DAO/Mapper接口-->
<mapper namespace="com.y.dao.UserDao">
    <!--select查询语句
    id对应接口中的一个方法名
    resultType对应这个方法返回的数据类型
    -->
    <select id="getUserList" resultType="com.y.pojo.User">
        select * from user
    </select>
</mapper>

resultType注意:

  • 如果接口中的这个方法返回的是集合,则只需要设为该集合的泛型类名即可
  • 如果返回类型是引用类型,则SQL查询结果集将传给该类中与结果集字段名相同的属性。例如这个例子中,结果集中只有字段名为id、name和pwd的查询内容会分别传给User对应的属性
  • 为了应对Java jdk中原始类型的命名重复,采取了特殊的命名风格,见Mybatis中常见的 Java 类型内建的类型别名

(2)在核心配置文件中注册Mapper:

<?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://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123321"/>
            </dataSource>
        </environment>
    </environments>
    <!--每一个Mapper.xml的路径都需要在核心配置文件中注册!-->
    <mappers>
        <mapper resource="com/y/dao/UserMapper.xml"/>
    </mappers>
</configuration>

(3)由于Mapper配置文件是在java目录下的,因此需要在pom.xml中把配置文件包含进去:

	<build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

调用Mapper方法的测试程序

  1. 测试

在test中构建与main目录结构相同的packect, 并在其相应的包下新建测试程序:
在这里插入图片描述

import java.util.List;

public class UserDaoTest {
    @Test
    public void test(){
        //获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //方式一: getMapper(常用方式)
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> userList = userDao.getUserList();
        /*//方式二:
        List<User> userList = sqlSession.selectList("com.y.dao.UserDao.getUserList");*/
        for (User user:userList) {
            System.out.println(user);
        }
        //关闭sqlSession
        sqlSession.close();
    }
}

测试执行结果:

在这里插入图片描述

三、CRUD

如何写Mapper配置文件(例如上面的UserMapper.xml)

1. namespace

namespace的值要和Dao/Mapper接口的名字一致:包名.文件名. 注意这并不是路径.
namespace不是必须的,但最好有,防止不同的接口出现相同的方法名(id)。

2. select

  • id: 对应namespace中的方法名
  • resultType: Sql语句执行的返回类型
  • parameterType: 传入方法参数的类型

举例:

    <select id="getUserById" resultType="com.y.pojo.User" parameterType="int">
        select * from user where id = #{id}
    </select>

#{}中的id是传参, 另一个id是数据库的字段名

#{} 和 ${} 的区别是什么?
· $ {}是 properties 文件中的变量占位符,它可以用于标签属性值和 sql 内部,详见二、2
· #{} 是 sql 的参数占位符,用于给 sql 设置参数值
· 可以用${}代替#{},但后者可以防止SQL注入的问题

    //根据id查询用户
    User getUserById(int id);

3. insert

<!--对象中的属性可以直接取出来-->
    <insert id="addUser" parameterType="com.y.pojo.User">
        insert into user (id, name, pwd) values (#{id}, #{name}, #{pwd})
    </insert>
    //插入一个用户
    int addUser(User user);

注意:

  • 执行增删改的方法后记得提交事务!
  • 增删改没有resultType字段
  • 只有一个传参且为基本类型参数时, 可以省略parameterType字段

4. update

    <update id="updateUser" parameterType="com.y.pojo.User">
        update user set name=#{name}, pwd=#{pwd} where id=#{id};
    </update>
    //修改用户
    int updateUser(User user);

5. delete

    <delete id="deleteUser" parameterType="int">
        delete from user where id=#{id}
    </delete>
    //删除用户
    int deleteUser(int id);

重要技巧之当实体类或数据库表中的属性或字段过多时, 我们最好考虑使用Map

    <insert id="addUser2" parameterType="Map">
        insert into user (id, name, pwd) values (#{userid}, #{username}, #{password})
    </insert>
    //万能的Map
    int addUser2(Map<String, Object> map);
    @Test
    public void testInsert2(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper userDao = sqlSession.getMapper(UserMapper.class);
        Map<String, Object> map = new HashMap<>();
        map.put("userid", 1);
        map.put("username", "ycr");
        map.put("password", "123321");
        int res = userDao.addUser2(map);
        //提交事务
        sqlSession.commit();
        sqlSession.close();
    }

用Map传递参数时可以在sql中直接取key;
用对象传递参数时可以在sql中直接取属性名.

6. like(模糊查询)

    //LIKE模糊查询
    List<User> getUserLike(String name);
    <select id="getUserLike" resultType="com.y.pojo.User" parameterType="String">
        select * from user where name like #{name}
    </select>
    @Test
    public void testSelectLike(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper userDao = sqlSession.getMapper(UserMapper.class);
        List<User> userList = userDao.getUserLike("%zz%");
        for (User user: userList) {
            System.out.println(user);
        }
        sqlSession.close();
    }

或者在UserMapper.xml中写成like “%”#{name}“%”, 并且在测试方法中调用方法时传入"zz".

四、 配置(configuration)

详见官网xml配置

核心配置文件(mybatis-config.xml)

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

configuration(配置)
	properties(属性)
	settings(设置)
	typeAliases(类型别名)
	typeHandlers(类型处理器)
	objectFactory(对象工厂)
	plugins(插件)
	environments(环境配置)
		environment(环境变量)
			transactionManager(事务管理器)
			dataSource(数据源)
	databaseIdProvider(数据库厂商标识)
	mappers(映射器)

注意: 标签顺序必须跟上面的一致

1. environments(环境配置)

MyBatis 可以配置成适应多种环境(environment, 环境变量). 不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

默认的transactionManager(事务管理器)是JDBC, dataSource(数据源)是POOLED.

2. properties(属性)

我们可以通过该字段实现引用配置文件。
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。

(1)在典型的 Java 属性文件中配置
resource/db.properties:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&;characterEncoding=utf-8&;useSSL=true
username=root
password="123321

核心配置文件:

	<!--引入外部配置文件-->
    <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>

(2)在 properties 元素的子元素中设置:

    <!--引入外部配置文件-->
    <properties>
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true"/>
        <property name="username" value="root"/>
        <property name="password" value="123321"/>
    </properties>

也可以同时设置配置文件, 但如果配置文件中的字段名和properties中的重复了, 系统会优先读取配置文件中的

    <!--引入外部配置文件-->
    <properties resource="db.properties">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true"/>
        <property name="username" value="root"/>
        <property name="password" value="111111"/>
    </properties>

environments标签内容与前面的一样

3. typeAliases(类型别名)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

    <!--给实体类取别名-->
    <typeAliases>
        <typeAlias alias="User" type="com.y.pojo.User"/>
    </typeAliases>

当这样配置时,User 可以用在任何使用 com.y.pojo.User 的地方。

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

    <typeAliases>
        <package name="com.y.pojo"/>
    </typeAliases>-->

每一个在包 com.y.pojo 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 com.y.pojo.User 的别名为 user;(原名也可以)若有注解,则别名为其注解值。见下面的例子:

package com.y.pojo;
import lombok.Data;

import org.apache.ibatis.type.Alias;

@Alias("user")
@Data
public class User {

第一种和第三种方法可以任意设置别名,但第二种不行。

4. mappers(映射器)

我们需要告诉 MyBatis 到哪里去找到这些语句。

方法一(绑定配置文件):

    <mappers>
        <mapper resource="com/y/dao/UserMapper.xml"/>
    </mappers>

方法二(绑定接口):

		<mapper class="com.y.dao.UserMapper"/>

方法三(扫描包):

        <package name="com.y.dao"/>

注意:
用class文件或扫描包这两种方法进行绑定注册时,接口和他的Mapper配置文件必须同名、同包。除非resources中的Mapper配置文件所在的目录结构与java目录下接口所在的包结构相同,这样它俩就会被打包到同一个classpath下,让Mybatis可以找到这两个文件:
在这里插入图片描述

五、作用域(Scope)和生命周期

一旦创建了 SqlSessionFactory,就不再需要SqlSessionFactoryBuilder了。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。因此 SqlSessionFactory 的最佳作用域是应用作用域。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。

六、 resultMap(结果集映射)

把pojo/User.java中的属性pwd改成password, 数据库中的pwd字段保持不变.
此时,
除了应用SQL中的AS语句外, 还可以通过在Mapper配置文件中的resultMap字段对实体类的属性和数据库的字段名建立映射关系.

Mapper配置文件:

<mapper namespace="com.y.dao.UserMapper">
    <select id="getUserById" resultType="com.y.pojo.User" parameterType="int" resultMap="UserMap">
        select * from user where id = #{id}
    </select>
    <resultMap id="UserMap" type="User">
        <!--column为数据库中的字段名,property为实体类中的属性-->
        <result column="pwd" property="password"/>
    </resultMap>
</mapper>

七、日志

日志工厂

如果数据库操作出现了异常,需要排错,就需要日志。其他常见排错方式有控制台输出、debug。

在这里插入图片描述

在这里插入图片描述

1. STDOUT_LOGGING

在核心配置文件中增加settings标签:

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

就可以看到控制台输出日志

2. LOG4J

  • LOG4J是apache的一个开源项目,它可以控制日志信息输送的目的地,例如控制台、文件、GUI组件
  • 我们可以控制每一条日志的输出格式
  • 通过定义每一条日志信息的级别,我们能更细致的控制日志的生成过程
  • 通过一个配置文件来灵活的进行配置,而不需要修改应用的代码

使用:

  1. 导包
  2. 写一个.properties文件

内容如下

#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4i.appender.console=org.apachelog4i.ConsoleAppender 
log4j.appender.consoleTarget=System.out log4j.appender.consoleThreshold=DEBUG
log4j.appender.console.layout=org.apachelog4j.PatternLayout
log4j.appender.consolelayoutConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file=org.apachelog4j.RollingFileAppender
log4j.appender.fileFile=/log/kuang.log
log4j.appender.fileMaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apachelog4j.PatternLayout
1og4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sq1=DEBUG
log4j.logger.java.sq1.statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sq1.Preparedstatement=DEBUG
  1. 修改核心配置文件
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
  1. 在要使用日志的类中,
import org.apache.log4j.Logger
static Logger logger = Logger.getLogger(UserDaoTest.class);

logger的作用域设为这个类, UserDaoTest为这个类的名字

在该类的方法中可以增加日志输出:

logger.info("info日志");
logger.debug("debug日志");
logger.error("error日志");

八、分页

1. 用SQL的limit实现分页

UserMapper接口:

    //分页
    List<User> getUserByLimit(Map<String, Integer> map);

UserMapper配置文件:

    <select id="getUserByLimit" resultType="User" parameterType="map" resultMap="UserMap">
        select * from user limit #{startIndex}, #{pageSize}
    </select>

测试程序:

    @Test
    public void getUserByLimit(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper userDao = sqlSession.getMapper(UserMapper.class);
        Map<String, Integer> map = new HashMap<>();
        map.put("startIndex", 0);
        map.put("pageSize", 3);
        List<User> userList = userDao.getUserByLimit(map);
        for (User user:userList) {
            System.out.println(user);
        }
        sqlSession.close();
    }

注意学会使用Map。

2. Mybatis的RowBounds对象

public RowBounds(int offset, int limit)
<E> List<E> selectList(String var1, Object var2, RowBounds var3);

3. 分页插件PageHelper

使用方法

九、使用注解开发

1. 面向接口编程

主要优点:解耦

三个面向的区别:

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

2. 使用注解开发

本质:反射机制实现
核心:动态代理

  1. 在接口上实现注解
    @Select("select * from user")
    List<User> getUsers();
  1. 在核心配置文件中绑定接口
    <!--绑定接口-->
    <mappers>
        <mapper class="com.y.dao.UserMapper"/>
    </mappers>

使用注解分别实现增删改查:

  1. 如果要使用增删改,可以在工具类获取sqlSession的方法中统一设置自动提交事务
public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession(true);
    }
  1. 接口:
    @Select("select * from user where id=#{ID}")
    User getUserById(@Param("ID") int id);

    @Insert("insert into user(id, name, pwd) values(#{id}, #{name}, #{password}) ")
    int addUser(User user);
    
    @Update("update user set name=#{name} where id=#{id}")
    int updateUser(User user);

    @Delete("delete from user where id=#{ID}")
    int deleteUser(@Param("ID")int id);

@Param(“”)注解一般用在基本类型和String参数上,引用类型不需要

  1. 在核心配置文件中绑定接口

十、Mybatis底层及执行流程

在这里插入图片描述
在这里插入图片描述

debug:
在这里插入图片描述
Mapper中的sqlSession对象含有数据库配置,mapperInterface获取相应的接口(UserMapper),methodCache获取接口中要执行的方法

在这里插入图片描述

执行流程:

Created with Raphaël 2.3.0 Resource类获取全局配置文件mybatis-config.xml 实例化SqlSessionFactoryBuilder 用build方法调用XMLConfigBuilder方法解析配置文件,根据配置信息创建configuration字段 实例化SqlSessionFactory transactional事务管理 创建executer执行器(包含transactional事务管理) 创建SqlSession对象 获取Mapper,然后实现CRUD 查看是否执行成功 提交事务 关闭SqlSession对象 yes no yes no

十、多对一和一对多处理

注意:保证SQL的可读性;属性名和字段名的对应问题

1. 多对一

要返回的pojo类中的一个属性是另外一个pojo类,且这两个类的数据要从不同的表中查询

现有两个表student和teacher,分别如下,
在这里插入图片描述
在这里插入图片描述
要求是在查询student时,根据student的tid字段同时查询对应的teacher表项并一起返回。

com.y.student:

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

com.y.teacher略。

方式一:按照查询嵌套处理(对应SQL的子查询/嵌套查询)

StudentMapper.xml:

    <select id="getStudent" resultMap="Teacher_of_Student">
        select * from student
    </select>
    <resultMap id="Teacher_of_Student" type="Student">
        <!--如果返回的类属性是一个
        引用对象:association
        集合:collection
        -->
        <!--ofType用来指定映射到集合中泛型的引用类型-->
        <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
        <!--<collection property=""/>-->
    </resultMap>
    <!--子查询(嵌套查询)SQL-->
    <!--#{}内传入的值是resultMap-association中column指示的参数,因此里面可以任意填-->
    <select id="getTeacher" resultType="Teacher">
        select * from teacher where id = #{id}
    </select>

方式二:按照结果嵌套处理(对应SQL的多表查询)

StudentMapper.xml:

    <!--多表查询SQL-->	
    <select id="getStudent2" resultMap="Teacher_of_Student_2">
        select s.id as id, s.name as name, t.id as tid, t.name as tname
        from student as s, teacher as t
        where s.tid=t.id
    </select>
    <resultMap id="Teacher_of_Student_2" type="Student">
        <!--result字段不能省略,否则返回结果为空-->
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <association property="teacher" javaType="Teacher">
            <result property="id" column="tid"/>
            <result property="name" column="tname"/>
        </association>
    </resultMap>

测试结果:
在这里插入图片描述

2. 一对多

要返回的pojo类中的一个属性是另外一个pojo类对象的集合,且这两个类的数据要从不同的表中查询

保持teacher表不变,student表增加表项:
在这里插入图片描述
保持Student类不变,给Teacher类增加一个泛型为Student的集合类属性:

@Data
public class Teacher {
    private int id;
    private String name;
    private List<Student> students;
}

方式一

TeacherMapper.xml:

    <select id="getTeacher2" resultMap="Student_of_Teacher2">
        select * from teacher
    </select>
    <resultMap id="Student_of_Teacher2" type="Teacher">
        <!--id字段被collection占用,因此要对它进行映射-->
        <result property="id" column="id"/>
        <!--ofType用来指定映射到集合中泛型的引用类型-->
        <collection property="students" ofType="Student" column="id" select="getStudents"/>
    </resultMap>
    <select id="getStudents" resultType="Student">
        select * from student where tid=#{id}
    </select>

方式二

TeacherMapper.xml:

    <select id="getTeacher" resultMap="Student_of_Teacher">
        select t.id id, t.name name, s.id sid, s.name sname
        from teacher t, student s
        where t.id = s.tid
    </select>
    <resultMap id="Student_of_Teacher" type="Teacher">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <collection property="students" ofType="Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="id"/>
        </collection>
    </resultMap>

测试结果:
在这里插入图片描述

十一、动态SQL

动态SQL就是根据不同的条件生成不同的SQL语句,避免手动拼接SQL。

先写基本的程序和数据:

  1. 数据库

在这里插入图片描述

  1. 实体类
package com.y.pojo;

import lombok.Data;
import java.util.Date;
import java.util.function.DoubleToIntFunction;

@Data
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date createTime;
    private int views;
}
  1. 数据库中的字段名映射到Java类的驼峰命名

在核心配置文件中:

    <settings>
        <!--从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn(驼峰命名法)。-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
  1. 生成UUID随机数作为id字段的值

重要技巧之用SuppressWarnings注解抑制警告

package com.y.utils;

import org.junit.Test;
import java.util.UUID;

@SuppressWarnings("all")//抑制所有类型的警告
public class IDutils {
    public static String getId(){
    	/*.replaceAll将字符串中的 - 全部替换为空字符*/
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
}
  1. 插入数据

Mapper接口、配置文件和执行程序省略,执行程序中id字段值获取方式如下

        Blog blog = new Blog();
        blog.setId(IDutils.getId());

if

根据测试程序传入的参数拼接查询条件,如果test程序中的map中没有title和author这两个键或这两个键的值,就执行select * from blog where 1=1;如果满足if标签中test的语句为true,就拼接SQL语句并执行。

Mapper配置文件:

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

test程序:

    @Test
    public void testQueryBlogIF(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        HashMap<Object, Object> map = new HashMap<>();
        map.put("title", "Mybatis");
        List<Blog> blogs = sqlSession.getMapper(BlogMapper.class).queryBlogIF(map);
        for (Blog blog:blogs) {
            System.out.println(blog);
        }
        sqlSession.close();
    }

结果:
在这里插入图片描述

Dao 接口里的方法,参数不同时,能重载吗?
Dao 接口里的方法可以重载,但是xml映射文件里面的id 不允许重复,即重载的多个方法必须用同一个id,然后用动态SQL的if实现方法的重载。

choose、when、otherwise

choose-when-otherwise相当于Java里的switch-case:顺序判断when的test语句,先满足条件的进行拼接;如果都不满足,直接拼接otherwise的SQL。

    <select id="queryBlogWHERE" parameterType="map" resultType="Blog">
        select * from blog where 1=1
        <choose>
            <when test="title != null">
                AND title = #{title}
            </when>
            <when test="author != null">
                AND author = #{author}
            </when>
            <otherwise>
                AND id=#{id}
            </otherwise>
        </choose>
    </select>

trim(where、set)

将上一个的“1=1”去掉,就需要用where元素:

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

注意:where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句,而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除;如果where元素里的条件都不满足,则相当于没有where。

update需要用set元素:

    <update id="updateBlogSET" parameterType="map">
        update blog
        <set>
        	/*注意set语句中的每一个等式中间需要用逗号隔开*/
            <if test="title != null">
                title = #{title},
            </if>
            <if test="author != null">
                author = #{author}
            </if>
        </set>
        where id=#{id}
    </update>

注意:set元素会自动删除多余的逗号

SQL片段

上面的几个SQL映射元素中有一部分是重复使用的,我们可以:用sql元素抽取公共的部分,然后在需要使用的地方用include标签引用:
注意,被引用的标签依然可以定义在任何地方,可以在引用标签的前面或者后面,MyBatis 都可以正确识别。

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

foreach

    <select id="queryBlogFOREACH" resultType="Blog" parameterType="map">
        SELECT * FROM Blog
        <where>
            <foreach item="id" collection="ids"
                     open="(" separator="or" close=")" nullable="true">
                id=#{id}
            </foreach>
        </where>
    </select>

这段动态sql相当于SELECT * FROM Blog WHERE (id=#{id} or id=#{id} or ......)
其中item的值必须和#{}中间的一样,是集合中的每一项,collection是集合名。

测试程序:

    @Test
    public void testQueryBlogFOREACH(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        HashMap<String, List> map = new HashMap<>();
        LinkedList<String> ids = new LinkedList<>();
        ids.add("ec64aedf13ab47e5beb0e534efaee0fe");
        ids.add("ed74e7f370de4b19b7ad3c5b0bbffd61");
        map.put("ids", ids);
        List<Blog> blogs = sqlSession.getMapper(BlogMapper.class).queryBlogFOREACH(map);
        for (Blog blog:blogs) {
            System.out.println(blog);
        }
        sqlSession.close();
    }

最终执行的SQL语句是SELECT * FROM Blog WHERE (id='ec64aedf13ab47e5beb0e534efaee0fe' or id='ed74e7f370de4b19b7ad3c5b0bbffd61')

十二、缓存

1. 简介

SQL查询连接数据库有一个耗资源的缺点,解决办法是把一次查询的结果暂时存储,再次查询相同的内容是就直接从这个缓存里查找。
使用缓存可以减少和数据库的交互次数,减少系统开销,提高系统效率。
什么样的数据库能使用缓存?查询多且改变少的数据库。

Mybatis系统中默认定义了两级缓存:一级缓存和二级缓存
默认情况下只有一级缓存开启(sqlSession级别的缓存,也称本地缓存)
二级缓存需要手动开启和配置,它是基于namespace级别的缓存
为了提高扩展性,Mybatis定义了缓存接口Cache。我们可以通过Cache来自定义二级缓存

idea技巧之快速查找文件

连按两下Shift,就会跳出搜索框,输入想查找的文件,例如jdk、导入的jar包等等当中的.class文件

2. 一级缓存

    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user1 = mapper.queryUserById(1);
        System.out.println(user1);
        System.out.println("=============");
        User user2 = mapper.queryUserById(1);
        System.out.println(user2);
        System.out.println(user1==user2);
        sqlSession.close();
    }

结果:
在这里插入图片描述

说明,重复查询的结果集内存地址相同!
两次查询中只有第一次进入了数据库查询,第二次是在内存中查询。

缓存失效的情况:

  1. 不同的查询
    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user1 = mapper.queryUserById(1);
        System.out.println(user1);
        System.out.println("=============");
        User user2 = mapper.queryUserByName("ycr");
        System.out.println(user2);
        System.out.println(user1==user2);
        sqlSession.close();
    }

结果:
在这里插入图片描述

  1. 用增删改改变了数据库。因为所有 insert、update 和 delete 语句会刷新缓存。

  2. 查询不同的Mapper.xml

  3. 手动清理缓存

    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user1 = mapper.queryUserById(1);
        System.out.println(user1);
        System.out.println("=============");
        sqlSession.clearCache();//手动清理缓存
        User user2 = mapper.queryUserById(1);
        System.out.println(user2);
        System.out.println(user1==user2);
        sqlSession.close();
    }

结果:false
进行了两次数据库查询。

因此,一级缓存默认开启,只在一次sqlSession中有效,也就是连接到关闭连接这个区间内有效。

底层原理:一级缓存维护的是一个Map

2. 二级缓存

  • 二级缓存也叫全局缓存。一级缓存的作用域太低,因此诞生了二级缓存
  • 一个命名空间对应一个二级缓存
  • 工作机制:
  • 如果一个会话关闭了,这个会话对应的一级缓存就没了,但是我们可以让一级缓存中的数据保存到二级缓存中;
  • 新的会话查询信息可以从二级缓存中获取;
  • 不同的mapper查出的数据会放在自己对应的缓存(map)中
  • 需要注意的是,只有当会话提交或者关闭的时候,才会存到二级缓存中

步骤:

  1. 开启全局缓存

方式一(不常用):通过核心配置文件的settings
在这里插入图片描述
方式二:通过映射文件的<cache/>标签。可以自定义缓存参数,详见官方文档。

  1. 测试

注意:序列化异常的解决办法是开启cache的只读:

<cache readOnly="true"/>
    @Test
    public void test(){
        SqlSession sqlSession1 = MybatisUtils.getSqlSession();
        User user1 = sqlSession1.getMapper(UserMapper.class).queryUserById(1);
        System.out.println(user1);
        sqlSession1.close();
        System.out.println("=============");
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();
        User user2 = sqlSession2.getMapper(UserMapper.class).queryUserById(1);
        System.out.println(user2);
        System.out.println(user1==user2);
        sqlSession2.close();
    }

结果:
在这里插入图片描述

3. Mybatis缓存原理

查询数据的顺序:二级缓存—>一级缓存—>数据库

4. 自定义缓存

可以通过实现Cache接口的方式自定义一种缓存, 详见官方文档. Ehcache是一种已实现的缓存.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值