开发框架-MyBatis

MyBatis

简介

环境:

  • JDK1.8
  • MySQL 5.7
  • Maven 3.6.1
  • IDEA

回顾:

  • JDBC
  • MySQL
  • Java基础
  • Maven
  • Junit

1 什么是 MyBatis?

image-20220113105923351

  • MyBatis 是一款优秀的持久层框架

  • 它支持自定义 SQL、存储过程以及高级映射。

  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了[google code](https://baike.baidu.com/item/google code/2346604),并且改名为MyBatis。

2013年11月迁移到Github。

2 如何获得MyBatis?

  • Maven仓库

<dependency>
    <groupId>org.mybatisgroupId>
    <artifactId>mybatisartifactId>
    <version>3.5.9version>
dependency>
  • GitHub:https://github.com/mybatis/mybatis-3
  • MyBatis手册:https://mybatis.org/mybatis-3/zh/index.html

3 持久化

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
  • 内存:断电即失
  • 数据库(JDBC)、IO文件持久化

为什么要持久化?

  • 有一些对象,不能让它丢掉
  • 内存对于大部分人来说,都是昂贵的存在

4 持久层

  • 完成持久化工作的代码层
  • 层界限十分明显

5 为什么需要MyBatis?

  • 帮助程序员将数据存入数据库中
  • 方便快捷
  • 传统的JDBC代码太复杂了。简化,框架,自动化。

优点:

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

第一个MyBatis程序

思路:搭建环境–>导入MyBatis–>编写代码–>测试

1 搭建环境

搭建数据库

CREATE DATABASE mybatis
USE `mybatis`
CREATE TABLE `user`(
 `id` INT(20) NOT NULL PRIMARY KEY,
 `name` VARCHAR(20) DEFAULT NULL,
 `pwd` VARCHAR(20) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
INSERT INTO `user`(`id`,`name`,`pwd`)
VALUES
(1, 'ayin1','123456'),
(2, 'ayin2','123456'),
(3, 'ayin3','123456'),
(4, 'ayin4','123456');	

2 编写MyBatis核心配置文件


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.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT"/>
                <property name="username" value="root"/>
                <property name="password" value="12345"/>
            dataSource>
        environment>
    environments>
    <mappers>
        <mapper resource="org/mybatis/example/BlogMapper.xml"/>
    mappers>
configuration>

3 编写MyBatis工具类

public class MyBatisUtils {
    public static SqlSessionFactory sqlSessionFactory;
    static {
        try {
            // 使用Mybatis的第一步,获取SqlSessionFactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 既然已经有了SqlSessionFactory对象,也就是说,已经建造起了存放SqlSession对象的工厂
    // 接下来就是从这个工厂里,拿出所需要的SqlSession对象即可
    public static SqlSession getSqlSession() {
        // SqlSession对象中包含面向数据库执行SQL命令所需的所有方法
        return sqlSessionFactory.openSession();
    }
}

4 编写代码

第一步 编写实体类

public class User {
    private Integer id;
    private String name;
    private String pwd;
    public User() {
    }
    public User(Integer id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPwd() {
        return pwd;
    }
    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}

第二步 编写Dao接口

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

第三步 编写对应接口类的Mapper.xml文件


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.atayin.dao.UserMapper">

    <select id="getUserList" resultType="com.atayin.pojo.User">
        select * from mybatis.user
    select>
mapper>

测试

public class UserDaoTest {
    @Test
    public void getUserList() {
        // 第一步:获取SqlSession对象
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        // 第二步:执行SQL
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> list = mapper.getUserList();
        for (User user : list) {
            System.out.println(user);
        }
        sqlSession.close();
    }
}

运行结果

开发框架-MyBatis_第1张图片

注意事项

  1. 每次编写完Mapper.xml文件之后,都需要在MyBatis.xml核心配置文件中进行注册。

    <mappers>
        
        <mapper resource="com/atayin/dao/UserMapper.xml"/>
    mappers>
    
  2. 关于Maven的资源导出问题:由于Maven是约定大于配置的,也就是说,在测试过程中有可能会遇到配置文件无法正确导出的问题,并在控制台报”无法找到配置文件“的错误,解决方案便是在pom.xml配置文件中加入build标签。

    
    <build>
      <resources>
        <resource>
          <directory>src/main/resourcedirectory>
          <includes>
            <include>**/*.propertiesinclude>
            <include>**/*.xmlinclude>
          includes>
        resource>
        <resource>
          <directory>src/main/javadirectory>
          <includes>
            <include>**/*.propertiesinclude>
            <include>**/*.xmlinclude>
          includes>
          <filtering>truefiltering>
        resource>
      resources>
    build>
    
  3. 更新Maven的配置文件之后需要刷新Maven。

  4. 在Mapper.xml配置文件的命名空间namespace中,绑定的是一个接口,而不是一个普通的类。

  5. SqlSessionFactoryBuilder类可以被实例化,调用build方法创建一个SqlSessionFactory,然后再通过”工厂“拿到SqlSession实例。

  6. SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。

  7. SqlSession 应当被及时关闭。

5 模糊查询

  1. Java代码执行的时候,传递通配符
// 模糊查询
List<User> getUserLists(String name);
<select id="getUserLists" resultType="com.atayin.pojo.User">
    select * from mybatis.user where name like #{value};
select>
  1. 在sql拼接中使用通配符
<select id="getUserLists" resultType="com.atayin.pojo.User">
    select * from mybatis.user where name like "%"#{value}"%";
select>
@Test
public void getUserLists(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> userLists = mapper.getUserLists("%ayin%");
    for (User userList : userLists) {
        System.out.println(userList);
    }
    sqlSession.close();
}

配置解析

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

  • configuration(配置)

    • properties(属性)

    • settings(设置)

    • typeAliases(类型别名)

    • typeHandlers(类型处理器)

    • objectFactory(对象工厂)

    • plugins(插件)

      • environments(环境配置)
        • environment(环境变量)
          • transactionManager(事务管理器)
          • dataSource(数据源)
    • databaseIdProvider(数据库厂商标识)

    • mappers(映射器)

环境配置

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

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

切换环境为测试环境:

<environments default="test">
    
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT"/>
            <property name="username" value="root"/>
            <property name="password" value="12345"/>
        dataSource>
        
    environment>
        <environment id="test">
        <transactionManager type="">transactionManager>
        <dataSource type="">dataSource>
    environment>
    
environments>

事务管理器(transactionManager)

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

  • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
  • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。

数据源(dataSource)

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

  • 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。

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

UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。

POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。

JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。

属性(Properties)

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

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

不过需要注意的是,在xml中,所有的标签都是可以规定顺序的,就比如说在mybatis的配置文件中,标签的顺序已经被规定,属性标签必须放在最前面,否则会报错。

db.prperties:

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT

mybatis.xml配置文件:


<properties resource="db.properties">
    <property name="username" value="root"/>
    <property name="password" value="12345"/>
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>

注意:

  1. propertie标签中可以直接通过resource子元素来引入外部配置文件
  2. 标签内也可以增加一些其他属性
  3. 如果xml文件和properties文件具有相同字段,优先使用外部配置文件!

类型别名(typeAliases)

  • 类型别名是为Java类型设置一个短的名字
  • 存在的意义仅用于减少类完全限定名的冗余
<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
typeAliases>

mybatis.xml配置文件:

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

UserMapper.xml配置文件:

<select id="getUserList" resultType="com.atayin.pojo.User">
    select * from mybatis.user
select>

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,并默认以这个类的类名(首字母小写)为别名,比如:

mybatis.xml配置文件:

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

UserMapper.xml配置文件:

<select id="getUserList" resultType="user">
    select * from mybatis.user
select>

当实体类比较少的时候,使用第一种方式;当实体类比较多时,使用第二种方式。第一种方式可以通过自己定义,第二种方式虽然无法自己定义,但仍然可以通过注解的方式来修改别名。

映射器

MapperRegistry:注册绑定我们的Mapper文件。

方式一:【推荐使用】

<mappers>

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

方式二:使用class文件进行注册绑定

<mappers>

	<mapper class="com.atayin.dao.UserMapper"/>
mappers>

方式三:使用扫描包进行注册绑定

<mappers>

	<package name="com.atayin.dao"/>
mappers>

方式二和方式三中,有以下注意事项:;

  • 接口和他的Mapper配置文件必须同名!
  • 接口和他的Mapper配置文件必须在同一个包下!

生命周期和作用域

不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

开发框架-MyBatis_第2张图片

SqlSessionFactoryBuilder

  • 这一旦创建了 SqlSessionFactory,就不再需要它了。

  • 局部变量

SqlSessionFactory

  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。

  • 可以简单理解为一个数据库连接池。

  • SqlSessionFactory 的最佳作用域是应用作用域。

  • SqlSessionFactory 的最佳作用域是应用作用域。

SqlSession

  • 连接到连接池的一个请求。
  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
  • 用完之后应当立刻关闭,否则会浪费资源。

解决属性名和字段名不一致的问题

制造数据库和字段名不一致的问题

数据库中的字段:

开发框架-MyBatis_第3张图片

新建一个项目,其中实体类字段要求与数据库字段不一致:

public class User {
    private Integer id;
    private String name;
    private String password;

当测试代码(输出数据库表所有行)运行后,可以发现,字段名不一致的字段输出的是null。

分析问题

在拷贝模块过程中,Mappe.xml配置文件里写的SQL语句为:

select * from mybatis.user

而类型处理器则会把上述SQL语句翻译为:

select id,name,pwd from mybatis.user

解决方法

方法一:起别名

select id,name,pwd as password from mybatis.user

这也是最暴力,最简单,最笨的方法,但并不意味着这是一个没用的方法。

方法二:使用结果集映射(ResultMap)

<mapper namespace="com.atayin.dao.UserMapper">
    <resultMap id="UserMap" type="User">
        
        <result column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="pwd" property="password"/>
    resultMap>

    
    <select id="getUserList" resultMap="UserMap">
        select * from mybatis.user
    select>
  • resultMap元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。

  • 上述语句只是简单地将所有的列映射到 HashMap 的键上,这由 resultType 属性指定。虽然在大部分情况下都够用,但是 HashMap 并不是一个很好的领域模型。你的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为领域模型。

  • ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

  • ResultMap的优秀之处——你完全可以不用显式地配置它们。

  • 如果这个世界总是这么简单就好了。

日志

1 日志工厂

如果一个数据库操作,出现了异常,我们需要排错时,日志就是最好的助手。

曾经程序出了问题我们可以通过sout或者debug来进行排错,而现在可以用更加方便快捷且直观的日志来协助我们进行排错,而Mybatis在XML配置中,Setting提供了一个日志设置。

设置名 描述 有效值 默认值
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J(deprecated since 3.5.9) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置

标准日志( STDOUT_LOGGING )输出:

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

日志输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tcKMqaTc-1646651756088)(C:/Users/12709/AppData/Roaming/Typora/typora-user-images/image-20220206163041978.png)]

2 Log4j

Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;

我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。

最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

1、首先需要导入Log4j的包。


<dependency>
    <groupId>log4jgroupId>
    <artifactId>log4jartifactId>
    <version>1.2.17version>
dependency>

2、log4j.properties

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

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

3、配置log4j的实现

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

4、log4j的使用(直接运行测试类即可)

5、简单使用

  • 在要使用Log4j的类中,导入包
import org.apache.log4j.Logger;
  • 日志对象,参数为当前类的class
static Logger logger = Logger.getLogger(UserMapperTest.class);
  • 日志级别
logger.info("输出级别");
logger.debug("DEBUG级别");
logger.error("ERROR级别");

分页

思考:为什么要分页?

  • 减少数据的处理量

使用Limit分页:

select * from table limit startPage,pageSize;

需要注意的是,起始页的下标是从0开始的。

小知识:曾经的MySQL关于这句语法的第二个参数是可以传入一个负值,可以将表中的行全部输出,但后来被公司发现,并确认为这是一个Bug,然后在之后的版本中被修复了。

使用MyBatis实现分页:

1、接口(UserMapper)

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

因为在SQL语句中,limit语句需要传入两个参数来进行,所以这边选择更加通用的Map来作为传数。

2、Mapper.xml

<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
    select * from mybatis.user limit #{startPage},#{pageSize};
select>

需要注意的是,所需的参数是map,而返回的结果集需要进行结果集映射。当然这只是避免麻烦所以才延续上面的代码继续进行测试。

3、测试

@Test
public void limitTest() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    HashMap<String, Integer> map = new HashMap<>();
    map.put("startPage", 0);
    map.put("pageSize", 2);
    List<User> limit = mapper.getUserByLimit(map);
    for (User user : limit) {
        System.out.println(user);
    }
    sqlSession.close();
}

使用注解开发

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

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

测试:

在使用注解后,需要更改mybatis配置文件中的注册方式,因为该方法并不需要建立一个Mapper.xml配置文件。

<mappers>
    <mapper class="com.atayin.dao.UserMapper"/>
mappers>

使用注解实现查找所有用户:

@Select("select * from mybatis.user")
List<User> getUserList();

经过测试,可以发现这是可以运行的,但是存在一些问题,比如说属性名和字段名不一致的问题:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yqw2tUip-1646651756089)(C:/Users/12709/AppData/Roaming/Typora/typora-user-images/image-20220222160002108.png)]

所以说:

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

选择何种方式来配置映射,以及认为是否应该要统一映射语句定义的形式,完全取决于你和你的团队。

永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。

思考:使用注解方式进行开发,它是怎么样运行的?

  • 本质:反射机制实现
  • 底层:动态代理

使用注解进行增删改查

根据之前的学习可以知道,当表格进行删改操作时,需要提交数据,表格才会实时更新。而在设置中,也可以将提交设置为自动提交,这样就不需要再进行手动提交。

sqlSessionFactory.openSession()重载了很多种方法,当填入参数true的时候,就将自动提交打开了。

public static SqlSession getSqlSession() {
    // SqlSession对象中包含面向数据库执行SQL命令所需的所有方法
    return sqlSessionFactory.openSession(true);
}

有关于注解@Param的说明:

@Select("select * from mybatis.user where id = #{id}")
User getUserByID(@Param("id") int id);

以上实例为查找用户表中指定id的用户,SQL语句中,参数名为id,而该参数严格按照@Param的参数来执行的,也就是说,当你的方法参数为id,而@Param()中的参数为其他,则会报错。

建议当某个方法存在多个参数时,加上@Param注解(一般用于基本类型参数和String类型)。

更新(增、删、改)操作:

@Update("insert into mybatis.user(id, name, pwd) values(#{id}, #{name}, #{password});")
int addUser(User user);

增删改查的具体操作并不难,只有SQL语句的区别的区别,但在前面,已经设置了自动提交,所以在进行测试时并不需要进行手动提交。

注意:需要在mybatis配置文件中的绑定接口!

Lombok

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.

Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.

lombok可以通过简单的注解的形式来帮助我们简化和消除一些必须有但显得很臃肿的Java代码,比如常见的Getter&Setter、toString()、构造函数等等。lombok不仅方便编写,同时也让我们的代码更简洁。

lombok提供了一个功能完整的jar包,可以很方便的与我们的项目进行集成。

1、安装Lombok插件

通过Settings/Plugins路径,在搜索框内搜索Lombok,并安装即可!

开发框架-MyBatis_第4张图片

可以看到右侧的插件简述中,列出了该插件提供的注释。使用较为频繁的注释有:

  • Date
  • AllArgsConstructor
  • NoArgsConstructor

2、导入Lombok所需的包


<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
    <version>1.18.22version>
    <scope>providedscope>
dependency>

具体如何使用Lombok,此时可以将视角转向实体类。

实体类里,一般会有一些变量,有参/无参构造方法、以及get/set方法、equals方法等等,虽然现在的编译工具已经提供了能够快速生成方法的快捷方式,不过人总是喜欢在偷懒这方面追求极致。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
    private String password;
}

仅需上面三行注解,这个实体类就会附带以上所述的所有方法!同理,当你使用@Get注解的时候,该实体类也会生成所有的Get方法!

注意:当你仅使用@Data、@AllArgsConstructor这两行注解时,是没有无参构造方法的,因为构造方法传参之后,原本默认的无参构造方法将会被覆盖。

缺点:大大降低了源代码的可读性和完整性,降低了阅读代码的舒适度。但插件始终也是工具,再强大的工具也要看是谁使用,代码需要给谁看,一味追求高大上的技术,反而会陷入思维的盲区,寻求适合项目的技术,才是正统王道。

复杂查询

1 多对一处理

环境搭建:

1、导入lambok

2、建立实体类Teacher、Student

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private int id;
    private String name;
    // 学生需要关联一个老师
    private Teacher teacher;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private int id;
    private String name;
}

3、建立Mapper接口

4、建立Mapper.xml文件

5、在MyBatis的核心配置文件绑定接口

6、测试

SQL语句创建对应表

CREATE TABLE `teacher` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师'); 

CREATE TABLE `student` (
  `id` INT(10) NOT NULL,
  `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');

嵌套查询处理:

StudentMapper.xml

具体实现思路:

  • 查询全部学生
  • 根据查询出来的学生tid,寻找对应的老师!
<select id="getStudent" resultMap="StudentMap">
    select * from student
select>

<resultMap id="StudentMap" type="Student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
resultMap>

    <select id="getTeacher" resultType="Teacher">
        select * from teacher where id = #{tid}
    select>

注意:因为学生实体类有一个Teacher对象,所以输出的结果是Teacher为null的,以上实例只是解决了该问题而已,可以当成一个比较复杂的属性名不一致的问题。也可以理解为SQL查询语句中的子查询。

结果集嵌套查询:

StudentMapper.xml

<select id="getStudent1" resultMap="StudentMap1">
    select s.id sid,s.name sname,t.name tname
    from student s,teacher t
    where s.tid = t.id;
select>

<resultMap id="StudentMap1" type="Student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="Teacher">
        <result property="name" column="tname"/>
    association>
resultMap>

一对多处理

建立实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private int id;
    private String name;
    // 老师拥有多个学生
    private List<Student> students;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private int id;
    private String name;
    private int tid;
}

结果集嵌套查询:

注意:本次实体类中,老师的实体类里面有一个集合,而遍历是遍历集合中的泛型对象,也就是学生对象,因此,在结果集映射中,需要用到的标签为而不是!

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

小结:

  • 关联(association)对应关系为多对一

  • 集合(collection)对应关系为一对多

  • javaType用来指定实体列中属性的类型,而ofType用来指定映射到List或者集合中的pojo类型,泛型中的约束类型!

动态SQL

什么是动态SQL?

动态SQL就是指根据不同的条件生成不同的SQL语句。

测试环境搭建:

1、导入lambok

2、建立实体类Teacher、Student

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Blog {
    private int id;
    private String title;
    private String author;
    private Date createTime; // 属性名和字段名不一致
    private int views;
}

3、建立Mapper接口

4、建立Mapper.xml文件

5、在MyBatis的核心配置文件绑定接口

SQL语句创建对应表

CREATE TABLE `blog`(
    `id` VARCHAR(50) NOT NULL COMMENT '博客id',
    `title` VARCHAR(100) NOT NULL COMMENT '博客标题',
    `author` VARCHAR(30) NOT NULL COMMENT '博客作者',
    `create_time` DATETIME NOT NULL COMMENT '创建时间',
    `views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8

注意:可以看到SQL中的字段名和实体类中的属性名不一致,但是MyBatis的settings可以设置一项功能,专门针对该情况。

设置名 描述 有效值 默认值
mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 true | false False
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
settings>

工具类:

编写一个工具类来实现随机ID号:

public class IDUtils {
    public static String getID() {
         return UUID.randomUUID().toString().replaceAll("-", "");
    }   
}

6、测试(插入数据)

public void addBlogTest() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Blog blog = new Blog();
    blog.setId(IDUtils.getID());
    blog.setTitle("Mybatis");
    blog.setAuthor("狂神说");
    blog.setCreateTime(new Date());
    blog.setViews(9999);

    mapper.addBlog(blog);

    blog.setId(IDUtils.getID());
    blog.setTitle("Java");
    mapper.addBlog(blog);

    blog.setId(IDUtils.getID());
    blog.setTitle("Spring");
    mapper.addBlog(blog);

    blog.setId(IDUtils.getID());
    blog.setTitle("微服务");
    mapper.addBlog(blog);

    sqlSession.close();
}

if

需求:当方法不传入参数时,输出所有博客;当方法传入参数时,输出指定博客。

接口:

List<Blog> queryBlogIf(Map map);

BlogMapper.xml:

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

注意:SQL语句后面的1=1可以保证不干扰正常进行全部查询的情况下,方便后续的SQL语句拼接,也就是说,不加入1=1的话,add关键字的拼接就会变得非常麻烦。

测试:

@Test
public void queryBlogIF() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap map = new HashMap();
    map.put("title", "微服务");
    List<Blog> blogs = mapper.queryBlogIf(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();
}

trim,where,set

<select id="queryBlogChoose">
    select *from blog
    <where>
        <if test="title != null">
            title = #{title}
        if>
        <if test="author">
            and author = #{author}
        if>
    where>
select>

上述实例利用了where标签,当SQL语句执行时,会进行自动拼接,也就是说,当只进行author字段查询时,会将不符合规范的SQL语句尽进行自动更正。

<update id="update" parameterType="map" >
    update blog
    <set>
        <if test="author != null">
            author = #{author},
        if>
        <if test="title != null">
            title = #{title}
        if>
    set>
    where id = #{id}
update>

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

choose,when,otherwise

<select id="queryBlogChoose" parameterType="map" resultType="Blog">
    select *from blog
    <where>
        <choose>
            <when test="title != null">
                title = #{title}
            when>
            <when test="author != null">
                and author = #{author}
            when>
            <otherwise>
                and views = #{views}
            otherwise>
        choose>
    where>
select>

上述实例使用了when标签与otherwise标签,类似于Java中的if-else,而且需要注意的是,当条件都不满足时,会自动拼接otherwise标签中的SQL语句,假设所有条件都满足,只会拼接第一个when标签中的SQL语句。

SQL片段

有的时候,我们可以把重复的SQL代码取出,方便复用。

1、使用SQL标签抽取重复的SQL代码

<sql id="if-title-author">
    <if test="title != null">
        and title = #{title}
    if>
    <if test="author">
        and author = #{author}
    if>
sql>

2、在需要使用的地方进行include标签引用

<include refid="if-title-author">include>

foreach

假设此时有这样一条SQL语句:

select * from blog WHERE ( id = ? or id = ? or id = ? or id = ? )

当只输入一个id参数时,查出来的行就只有一行,当输入复数个id参数时,查出来的行也是对应复数的,这种情况下,可以使用foreach来进行SQL语句的拼接。

<select id="queryBlogForeach" parameterType="map" resultType="Blog">
    select * from blog
    <where>
        -- open为起始值,close为终止值,separator为插入值
        <foreach collection="ids" item="id" open="and (" close=")" separator="or">
            id = #{id}
        foreach>
    where>
select>

注意:where能够自动SQL语句中起始的and关键字,所以and关键字需要放入open属性里面。

HashMap map = new HashMap();
ArrayList<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
ids.add(3);
ids.add(4);
map.put("ids", ids);
List<Blog> list = mapper.queryBlogForeach(map);

根据测试可以发现,foreach会遍历集合ids,每遍历一次就会拿出存在集合中的id。

缓存

1、什么是缓存?

  • 存在内存中的临时数据。
  • 将用户经常查询的数据存在内存中,当用户需要查询数据的时候就不用从磁盘上去去查询,从缓存中查询,不仅提高了查询效率,而且还解决了高并发系统的性能问题。

2、为什么要用缓存?

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

3、什么样的数据可以使用缓存?

  • 经常查询而且还是不经常改变的数据。

Mybatis缓存

  • MyBatis包含了一个非常强大的查询缓存特性,他可以非常方便地定制和配置缓存,缓存可以极大地提升查询效率。
  • Mybatis系统默认定义了两级缓冲:一级缓存和二级缓存。
    • 默认情况下,只有一级缓存开启(SqlSession级别的缓存,也成为了本地缓存)。
    • 二级缓存需要手动开启,他是基于namespace级别的缓存。
    • 为了提高扩展性,MyBatis提供了缓存接口Cache,可以通过该接口来自定义二级缓存。

一级缓存

测试在一个SqlSession中查询两次相同的记录:

@Test
public void queryUserById() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.queryUserById(1);
    User user1 = mapper.queryUserById(1);
    System.out.println(user == user1);
}

结果:true

当在两次查询中插入一次删改操作:

@Test
public void queryUserById() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.queryUserById(1);
    
    mapper.update(new User(2,"Java新时代","Ayin"))
    
    User user1 = mapper.queryUserById(1);
    System.out.println(user == user1);
}

结果:false

可以发现,即使执行更新的操作并不是进行比较的用户,也返回的是false,因为在系统在执行更新操作之后,刷新了缓存,也就是走了两次SQL语句,前后已经发生了变化,所以返回的是false。

缓存失效的情况:

1、查询不同的东西。

2、进行增删改操作,因为可能会改变原来的数据,所以必定刷新缓存。

3、查询不同的Mapper.xml。

4、手动清理缓存。

sqlSession.clearCache();

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

二级缓存

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

<cache/>
  • 基本上就是这样。这个简单语句的效果如下:
    • 映射语句文件中的所有 select 语句的结果将会被缓存。
    • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
    • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
    • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
    • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
    • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

提示: 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

这些属性可以通过 cache 元素的属性来修改。比如:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

二级缓存也被称作为全局缓存,一级缓存的作用域太低了,所以诞生了二级缓存。二级缓存也是基于namespace的缓存,一个空间名称,对应一个二级缓存。

二级缓存工作机制:

  • 一个会话查询一条数据,这条数据就会被放在当前会话的一级缓存中。
  • 如果当前会话关闭了,那么这个对应的一级缓存就没有了;但此时需求为:会话关闭,一级缓存保存至二级缓存中。
  • 新的会话查询信息,可以从二级缓存中获取。
  • 不同的Mapper查出的数据会放在自己对应的缓存(Map)中。

步骤:

1、显式开启全局缓存

设置名 描述 有效值 默认值
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true | false true

虽然这个设置是默认开启的,但是为了代码的可读性更强,所以写上这段。

<setting name="cacheEnabled" value="true"/>

2、在所需的Mapper.xml中使用二级缓存

<cache/>

也可以自定义参数:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

3、测试

SqlSession sqlSession = MyBatisUtils.getSqlSession();
SqlSession sqlSession1 = MyBatisUtils.getSqlSession();

UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
sqlSession.close();

UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = mapper1.queryUserById(1);
System.out.println(user == user1);
sqlSession1.close();

上述代码在未开启二级缓存时返回的结果是false,开启二级缓存后返回为true,当第一个SqlSession关闭之后,会将结果保存至二级缓存中,而第二个SqlSession进行查询操作时,便直接在二级缓存中进行查找。但前提是它们都在同一个Mapper中!

可能会遇到的问题:

  • 序列化异常:NotSerializableException

解决方法:实体类继承序列化接口!

public class User implements Serializable {

小结:

  • 只有开启了二级缓存,在同一个Mapper之下就有效。
  • 所有的数据都会优先放在一级缓存中!
  • 只有当会话提交,或者关闭之后,才会提交至二级缓存中!
    以诞生了二级缓存。二级缓存也是基于namespace的缓存,一个空间名称,对应一个二级缓存。

二级缓存工作机制:

  • 一个会话查询一条数据,这条数据就会被放在当前会话的一级缓存中。
  • 如果当前会话关闭了,那么这个对应的一级缓存就没有了;但此时需求为:会话关闭,一级缓存保存至二级缓存中。
  • 新的会话查询信息,可以从二级缓存中获取。
  • 不同的Mapper查出的数据会放在自己对应的缓存(Map)中。

步骤:

1、显式开启全局缓存

设置名 描述 有效值 默认值
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true | false true

虽然这个设置是默认开启的,但是为了代码的可读性更强,所以写上这段。

<setting name="cacheEnabled" value="true"/>

2、在所需的Mapper.xml中使用二级缓存

<cache/>

也可以自定义参数:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

3、测试

SqlSession sqlSession = MyBatisUtils.getSqlSession();
SqlSession sqlSession1 = MyBatisUtils.getSqlSession();

UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
sqlSession.close();

UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = mapper1.queryUserById(1);
System.out.println(user == user1);
sqlSession1.close();

上述代码在未开启二级缓存时返回的结果是false,开启二级缓存后返回为true,当第一个SqlSession关闭之后,会将结果保存至二级缓存中,而第二个SqlSession进行查询操作时,便直接在二级缓存中进行查找。但前提是它们都在同一个Mapper中!

可能会遇到的问题:

  • 序列化异常:NotSerializableException

解决方法:实体类继承序列化接口!

public class User implements Serializable {

小结:

  • 只有开启了二级缓存,在同一个Mapper之下就有效。
  • 所有的数据都会优先放在一级缓存中!
  • 只有当会话提交,或者关闭之后,才会提交至二级缓存中!

你可能感兴趣的:(笔记,maven,java,mybatis)