[记录学习]自学动力节点老杜MyBatis笔记_02

MyBatis自学笔记

首先感谢动力节点和杜老师的教学分享!Respect!
学习来源:B站
https://www.bilibili.com/video/BV1JP4y1Z73S?p=1&vd_source=07c8a1a7d89af39fe20c3a6894f5ff6a
资料来源:百度网盘
https://pan.baidu.com/s/1jFuopTaNmRfK4q0BnMfuBw?pwd=dljd
提取码:dljd

文章目录

  • MyBatis自学笔记
    • 六、在WEB中应用MyBatis(使用MVC架构模式)
      • 6.1 需求描述
      • 6.2 数据库表的设计和准备数据
      • 6.3 实现步骤
        • 第一步:环境搭建
        • 第二步:前端页面
        • 第三步:创建pojo包、service包、dao包、web包、utils包
        • 第四步:定义pojo类:Account
        • 第五步:编写AccountDao接口,以及AccountDaoImpl实现类
        • 第六步:AccountDaoImpl中编写了mybatis代码,需要编写SQL映射文件了
        • 第七步:编写AccountService接口以及AccountServiceImpl
        • 第八步:编写AccountController
      • 6.4 MyBatis对象作用域以及事务问题
        • MyBatis核心对象的作用域
        • 事务问题
      • 6.5 分析当前程序存在的问题
    • 七、使用javassist生成类
      • 7.1 Javassist的使用
      • 7.2 使用Javassist生成Proxy类(module以第六章为基础)
    • 八、MyBatis中接口代理机制及使用
        • 利用MyBatis中接口代理机制完成crud
    • 九、MyBatis小技巧
      • 9.1 #{}和${}
        • 使用#{}
        • 使用\${}
        • 什么情况下必须使用\${}
        • 拼接表名
        • 批量删除
        • 模糊查询
      • 9.2 typeAliases 别名
        • 第一种方式:typeAlias
        • 第二种方式:package
      • 9.3 mappers
        • resource
        • url
        • class
        • package
      • 9.4 idea配置文件模板
      • 9.5 插入数据时获取自动生成的主键
      • 课堂笔记
    • 十、MyBatis参数处理
      • 10.1 单个简单类型参数
      • 10.2 Map参数
      • 10.3 实体类参数
      • 10.4 多参数
      • 10.5 @Param注解(命名参数)
      • 10.6 @Param源码分析
    • 十一、MyBatis查询语句专题
      • 11.1 返回Car
      • 11.2 返回List
      • 11.3 返回Map
      • 11.4 返回List\
      • 11.5 返回Map\
      • 11.6 resultMap结果映射
        • 使用resultMap进行结果映射
        • 是否开启驼峰命名自动映射
      • 11.7 返回总记录条数
    • 十二、动态SQL
      • 12.1 if标签
      • 12.2 where标签
      • 12.3 trim标签
      • 12.4 set标签
      • 12.5 choose when otherwise
      • 12.6 foreach标签
        • 批量删除
        • 批量添加
      • 12.7 sql标签与include标签
    • 十三、MyBatis的高级映射及延迟加载
      • 13.1 多对一
        • 第一种方式:级联属性映射
        • 第二种方式:association
        • 第三种方式:分步查询
      • 13.2 多对一延迟加载
      • 13.3 一对多
        • 第一种方式:collection
        • 第二种方式:分步查询
      • 13.4 一对多延迟加载
    • 十四、MyBatis的缓存
      • 14.1 一级缓存
      • 14.2 二级缓存
        • 二级缓存的相关配置:
      • 14.3 MyBatis集成EhCache
    • 十五、MyBatis的逆向工程
      • 15.1 逆向工程配置与生成
        • 第一步:基础环境准备
        • 第二步:在pom中添加逆向工程插件
        • 第三步:配置generatorConfig.xml
        • 第四步:运行插件
      • 15.2 测试逆向工程生成的是否好用
        • 第一步:环境准备
        • 第二步:编写测试程序
    • 十六、MyBatis使用PageHelper
      • 16.1 limit分页
      • 16.3 PageHelper插件
        • 第一步:引入依赖
        • 第二步:在mybatis-config.xml文件中配置插件
        • 第三步:编写Java代码
    • 十七、MyBatis的注解式开发
      • 17.1 @Insert
      • 17.2 @Delete
      • 17.3 @Update
      • 17.4 @Select
    • 五、手写MyBatis框架(掌握原理)
      • 5.1 dom4j解析XML文件
      • 5.2 GodBatis
        • 第一步:IDEA中创建模块
        • 第二步:资源工具类,方便获取指向配置文件的输入流
        • 第三步:定义SqlSessionFactoryBuilder类
        • 第四步:分析SqlSessionFactory类中有哪些属性
        • 第五步:定义GodJDBCTransaction
        • 第六步:事务管理器中需要数据源,定义GodUNPOOLEDDataSource
        • 第七步:定义GodMappedStatement
        • 第八步:完善SqlSessionFactory类
        • 第九步:完善SqlSessionFactoryBuilder中的build方法
        • 第十步:在SqlSessionFactory中添加openSession方法
        • 第十一步:编写SqlSession类中commit rollback close方法
        • 第十二步:编写SqlSession类中的insert方法
        • 第十三步:编写SqlSession类中的selectOne方法
      • 5.3 GodBatis使用Maven打包
      • 5.4 使用GodBatis

六、在WEB中应用MyBatis(使用MVC架构模式)

目标:

  • 掌握mybatis在web应用中怎么用
  • mybatis三大对象的作用域和生命周期
  • ThreadLocal原理及使用
  • 巩固MVC架构模式
  • 为学习MyBatis的接口代理机制做准备

实现功能:

  • 银行账户转账

使用技术:

  • HTML + Servlet + MyBatis

WEB应用的名称:

  • bank

6.1 需求描述

[记录学习]自学动力节点老杜MyBatis笔记_02_第1张图片

6.2 数据库表的设计和准备数据

[记录学习]自学动力节点老杜MyBatis笔记_02_第2张图片
[记录学习]自学动力节点老杜MyBatis笔记_02_第3张图片

6.3 实现步骤

第一步:环境搭建

  • IDEA中创建Maven WEB应用(mybatis-004-web)

[记录学习]自学动力节点老杜MyBatis笔记_02_第4张图片

  • IDEA配置Tomcat,这⾥Tomcat使用10+版本。并部署应用到tomcat。

[记录学习]自学动力节点老杜MyBatis笔记_02_第5张图片
[记录学习]自学动力节点老杜MyBatis笔记_02_第6张图片

  • 默认创建的maven web应用没有java和resources目录,包括两种解决方案

    • 第一种:自己手动加上。

[记录学习]自学动力节点老杜MyBatis笔记_02_第7张图片

  • 第二种:修改maven-archetype-webapp-1.4.jar中的配置文件

[记录学习]自学动力节点老杜MyBatis笔记_02_第8张图片

[记录学习]自学动力节点老杜MyBatis笔记_02_第9张图片

[记录学习]自学动力节点老杜MyBatis笔记_02_第10张图片

  • web.xml文件的版本较低,可以从tomcat10的样例文件中复制,然后修改

    • 注:(本人采用的是java 1.8.261, tomcat 8.5.83)


<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1"
         metadata-complete="false">
    









web-app>
  • 删除index.jsp文件,因为我们这个项目不使用JSP。只使用html。
  • 确定pom.xml文件中的打包方式是war包。
  • 引入相关依赖
    • 编译器版本修改为17
    • 引入的依赖包括:mybatis,mysql驱动,junit,logback,servlet。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0modelVersion>
  <groupId>com.powernodegroupId>
  <artifactId>mybatis-004-webartifactId>
  <packaging>warpackaging>
  <version>1.0-SNAPSHOTversion>
  <name>mybatis-004-web Maven Webappname>
  <url>http://maven.apache.orgurl>
  <dependencies>
    
    <dependency>
      <groupId>org.mybatisgroupId>
      <artifactId>mybatisartifactId>
      <version>3.5.10version>
    dependency>
    
    <dependency>
      <groupId>mysqlgroupId>
      <artifactId>mysql-connector-javaartifactId>
      <version>8.0.30version>
    dependency>
    
    <dependency>
      <groupId>ch.qos.logbackgroupId>
      <artifactId>logback-classicartifactId>
      <version>1.2.11version>
    dependency>
    
    <dependency>
      <groupId>javax.servletgroupId>
      <artifactId>javax.servlet-apiartifactId>
      <version>4.0.1version>
    dependency>
    
    <dependency>
      <groupId>junitgroupId>
      <artifactId>junitartifactId>
      <version>4.13.2version>
      <scope>testscope>
    dependency>
  dependencies>
  <build>
    <finalName>mybatis-004-webfinalName>
      <plugins>
          <plugin>
              <groupId>org.apache.maven.pluginsgroupId>
              <artifactId>maven-compiler-pluginartifactId>
              <configuration>
                  <source>8source>
                  <target>8target>
              configuration>
          plugin>
      plugins>
  build>
project>
  • 引入相关配置文件,放到resources目录下(全部放到类的根路径下)

    • mybatis-config.xml
    
    DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <properties resource="jdbc.properties" />
        <environments default="powernode">
            <environment id="powernode">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
                    <property name="username" value="${jdbc.username}"/>
                    <property name="password" value="${jdbc.password}"/>
                    dataSource>
            environment>
        environments>
        <mappers>
            <mapper resource="AccountMapper.xml"/>
        mappers>
    configuration>
    
    • AccountMapper.xml

    • logback.xml

    • jdbc.properties

    jdbc.username=root
    jdbc.password=root
    

第二步:前端页面

  • index.html
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>银行账户转账title>
head>
<body>

<form action="/bank/transfer" method="post">
    转出账户:<input type="text" name="fromActno" /><br>
    转入账户:<input type="text" name="toActno" /><br>
    转账金额:<input type="text" name="money" /><br>
    <input type="submit" value="转账" />
form>
body>
html>
  • MoneyNotEnoughException.html
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>银行转账报告title>
head>
<body>
<h1>转账失败,余额不足!h1>
body>
html>
  • TransferException.html
DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>银行转账报告title>
head>
<body>
<h1>转账失败,未知错误!h1>
body>
html>
  • TransferSuccess.html
DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>银行转账报告title>
head>
<body>
<h1>转账成功!h1>
body>
html>

第三步:创建pojo包、service包、dao包、web包、utils包

  • com.powernode.bank.pojo
  • com.powernode.bank.service
  • com.powernode.bank.service.impl
  • com.powernode.bank.dao
  • com.powernode.bank.dao.impl
  • com.powernode.bank.web.controller
  • com.powernode.bank.exception
  • com.powernode.bank.utils:将之前编写的SqlSessionUtil工具类拷贝到该包下。

第四步:定义pojo类:Account

package com.powernode.bank.pojo;

/**
 * 银行账户类
 * @author ShiningSong
 * @version 1.0
 * @since 1.0
 */
public class Account {
    private Long id;
    private String actno;
    private Double balance;

    public Account() {
    }

    public Account(Long id, String actno, Double balance) {
        this.id = id;
        this.actno = actno;
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public Double getBalance() {
        return balance;
    }

    public void setBalance(Double balance) {
        this.balance = balance;
    }
}

第五步:编写AccountDao接口,以及AccountDaoImpl实现类

分析dao中⾄少要提供几个方法,才能完成转账:

  • 转账前需要查询余额是否充足:selectByActno
  • 转账时要更新账户:update
package com.powernode.bank.dao;

import com.powernode.bank.pojo.Account;

/**
 * 账户数据访问对象
 * @author ShiningSong
 * @version 1.0
 * @since 1.0
 */
public interface AccountDao {

    /**
     * 根据账号获取账户信息
     * @param actno 账号
     * @return Account 账户信息
     */
    Account selectByActno(String actno);

    /**
     * 更新账户信息
     * @param act 账户信息
     * @return 1表示更新成功,其他表示失败
     */
    int updateByActno(Account act);
}

package com.powernode.bank.dao.impl;

import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

public class AccountDaoImpl implements AccountDao {
    public Account selectByActno(String actno) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        Account act = (Account) sqlSession.selectOne("account.selectByActno", actno);
        sqlSession.close();
        return act;
    }

    public int updateByActno(Account act) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        int count = sqlSession.update("account.updateByActno", act);
        sqlSession.commit();
        sqlSession.close();
        return count;
    }
}

第六步:AccountDaoImpl中编写了mybatis代码,需要编写SQL映射文件了


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

<mapper namespace="com.powernode.bank.dao.AccountDao">
  <select id="selectByActno" resultType="com.powernode.bank.pojo.Account">
      select * from t_act where actno=#{actno};
  select>

  <update id="updateByActno">
      update t_act set balance=#{balance} where actno=#{actno}
  update>
mapper>

第七步:编写AccountService接口以及AccountServiceImpl

  • 自定义异常类

    • MoneyNotEnoughException
    package com.powernode.bank.exception;
    
    /**
     * 余额不足异常
     */
    public class MoneyNotEnoughException extends Exception{
        public void MoneyNotEnoughException(){}
    
        public MoneyNotEnoughException(String msg) {
            super(msg);
        }
    }
    
    • TransferException
    package com.powernode.bank.exception;
    
    /**
     * 转账失败异常
     */
    public class TransferException extends Exception{
        public TransferException() {
        }
    
        public TransferException(String message) {
            super(message);
        }
    }
    
  • AccountService接口以及AccountServiceImpl

package com.powernode.bank.service;


import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.exception.TransferException;

/**
 * 注意:业务类当中的业务方法的名字在起名的时候,最好见名知意,能够体现出具体的业务是做什么的。
 * 账户业务类
 * @author ShiningSong
 * @version 1.0
 * @since 1.0
 */
public interface AccountService {

    /**
     * 账户转账业务
     * @param fromActno 转出账号
     * @param toActno 转入账号
     * @param money 转账金额
     */
    void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException;
}
package com.powernode.bank.service.impl;

import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.exception.TransferException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.utils.GenerateDaoProxy;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

public class AccountServiceImpl implements AccountService {

    // 声明dao层的实现类
    private AccountDao accountDaoImpl = new AccountDaoImpl();

    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {

        // 1. 判断转出账户的余额是否充足(select)
        Account fromAct = accountDaoImpl.selectByActno(fromActno);
        // 2. 如果转出账户余额不足,提示用户
        if (fromAct.getBalance() < money) {
            throw new MoneyNotEnoughException("余额不足,转账失败!");
        }

        // 3. 如果转出账户余额充足,更新转出账户余额(update)
        fromAct.setBalance(fromAct.getBalance() - money);
        int count = accountDaoImpl.updateByActno(fromact);

        // 4. 更新转入账户余额(update)
        Account toAct = accountDaoImpl.selectByActno(toActno);
        toAct.setBalance(toAct.getBalance() + money);
        count += accountDaoImpl.updateByActno(toAct);
        if(2 != count){
            throw new TransferException("未知错误,转账失败!");
        }
    }
}

第八步:编写AccountController

package com.powernode.bank.web;

import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.exception.TransferException;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.service.impl.AccountServiceImpl;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {

    // 为了让其他方法也可以使用这个对象,声明为实例变量
    private AccountService accountService = new AccountServiceImpl();

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取form表单信息
        String fromActno = request.getParameter("fromActno");
        String toActno = request.getParameter("toActno");
        double money = Double.parseDouble(request.getParameter("money"));
        try {
            accountService.transfer(fromActno, toActno, money);
            // 转账成功页面
            response.sendRedirect(request.getContextPath() + "/TransferSuccess.html");
        } catch (MoneyNotEnoughException e) {
            // 余额不足页面
            response.sendRedirect(request.getContextPath() + "/MoneyNotEnoughException.html");
        } catch (TransferException e) {
            // 转账异常页面
            response.sendRedirect(request.getContextPath() + "/TransferException.html");
        } catch (Exception e){
            // 转账异常页面
            response.sendRedirect(request.getContextPath() + "/TransferException.html");
        }
    }
}
  • 启动服务器,打开浏览器,输入地址:http://localhost:8080/bank,测试:

[记录学习]自学动力节点老杜MyBatis笔记_02_第11张图片

[记录学习]自学动力节点老杜MyBatis笔记_02_第12张图片
[记录学习]自学动力节点老杜MyBatis笔记_02_第13张图片

6.4 MyBatis对象作用域以及事务问题

MyBatis核心对象的作用域

  • SqlSessionFactoryBuilder

    • 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

    • 你可以重用SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有 的 XML 解析资源可以被释放给更重要的事情。

  • SqlSessionFactory

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

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

事务问题

  • 在之前的转账业务中,更新了两个账户,我们需要保证它们的同时成功或同时失败,这个时候就需要使用事务机 制,在transfer方法开始执行时开启事务,直到两个更新都成功之后,再提交事务,我们尝试将transfer方法进行如下修改(在两次更新操作中添加异常):
package com.powernode.bank.service.impl;

import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.exception.TransferException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.utils.GenerateDaoProxy;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

public class AccountServiceImpl implements AccountService {

    // 声明dao层的实现类
    private AccountDao accountDaoImpl = new AccountDaoImpl();

    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {

        // 1. 判断转出账户的余额是否充足(select)
        Account fromAct = accountDaoImpl.selectByActno(fromActno);
        // 2. 如果转出账户余额不足,提示用户
        if (fromAct.getBalance() < money) {
            throw new MoneyNotEnoughException("余额不足,转账失败!");
        }

        // 3. 如果转出账户余额充足,更新转出账户余额(update)
        fromAct.setBalance(fromAct.getBalance() - money);
        int count = accountDaoImpl.updateByActno(fromact);
        
        // 模拟异常
        String exception = null;
        exception.toString();

        // 4. 更新转入账户余额(update)
        Account toAct = accountDaoImpl.selectByActno(toActno);
        toAct.setBalance(toAct.getBalance() + money);
        count += accountDaoImpl.updateByActno(toAct);
        if(2 != count){
            throw new TransferException("未知错误,转账失败!");
        }
    }
}
  • 运行前数据库数据:

[记录学习]自学动力节点老杜MyBatis笔记_02_第14张图片

  • 运行结果:

[记录学习]自学动力节点老杜MyBatis笔记_02_第15张图片

[记录学习]自学动力节点老杜MyBatis笔记_02_第16张图片

[记录学习]自学动力节点老杜MyBatis笔记_02_第17张图片

事务出问题了,转账失败了,钱仍然是少了1万。这是什么原因呢?主要是因为service 和dao中使用的SqlSession对象不是同一个。

  • 怎么办?为了保证service和dao中使用的SqlSession对象是同一个,可以将SqlSession对象存放到
    ThreadLocal当中。修改SqlSessionUtil工具类:
package com.powernode.bank.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.IOException;

/**
 * MyBatis工具类
 * @author Shanglinsong
 * @version 1.0
 * @sine 1.0
 */

public class SqlSessionUtil {

    // 工具类的构造方法一般都是私有化的。
    // 工具类中所有的方法都是静态的,直接采用类名调用即可,不需要new对象。
    // 为了防止new对象,构造方法私有化
    private SqlSessionUtil() {
    }

    private static SqlSessionFactory sqlSessionFactory;

    // 类加载时执行
    // SqlSessionUtil工具类在进行第一次加载的时候,解析mybatis-config.xml文件,创建SqlSessionFactory对象。
    static{
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // 全局的,服务器级别的,一个服务器当中定义一个即可。
    // 为什么把SqlSession对象放到ThreadLocal当中呢?为了保证一个线程对应一个SqlSession。
    private static ThreadLocal<SqlSession> local =  new ThreadLocal<SqlSession>();

    /**
     * 获取SqlSession对象
     * @return SqlSession 会话对象
     */
    public static SqlSession openSession(){
        SqlSession sqlSession = local.get();
        if (sqlSession == null) {
            sqlSession = sqlSessionFactory.openSession();
            // 将sqlSession对象绑定到当前线程上。
            local.set(sqlSession);
        }
        return sqlSession;
    }

    /**
     * 关闭会话,释放资源,同时与当前线程解绑
     * @param sqlSession 当前线程使用的会话
     */
    public static void close(SqlSession sqlSession){
        if (sqlSession != null) {
            sqlSession.close();
            // 注意移除SqlSession对象与当前线程的绑定关系。
            // 因为Tomcat服务器支持线程池。也就是说:用过的线程对象t1,可能下一次还会使用这个t1线程
            local.remove();
        }
    }
}

  • 修改dao中的方法:AccountDaoImpl中所有方法中的提交commit和关闭close代码全部删除:
package com.powernode.bank.dao.impl;

import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

public class AccountDaoImpl implements AccountDao {
    public Account selectByActno(String actno) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
//        Account act = (Account) sqlSession.selectOne("account.selectByActno", actno);
//        sqlSession.close();
//        return act;
        return (Account) sqlSession.selectOne("account.selectByActno", actno);
    }

    public int updateByActno(Account act) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
//        int count = sqlSession.update("account.updateByActno", act);
//        sqlSession.commit();
//        sqlSession.close();
//        return count;
        return sqlSession.update("account.updateByActno", act);
    }
}
  • 修改service中的方法:
package com.powernode.bank.service.impl;

import com.powernode.bank.dao.AccountDao;
//import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.exception.TransferException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.utils.GenerateDaoProxy;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

public class AccountServiceImpl implements AccountService {

    // 声明dao层的实现类
    private AccountDao accountDaoImpl = new AccountDaoImpl();

    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {

        // 开启会话
        SqlSession sqlSession = SqlSessionUtil.openSession();

        // 1. 判断转出账户的余额是否充足(select)
        Account fromAct = accountDaoImpl.selectByActno(fromActno);
        // 2. 如果转出账户余额不足,提示用户
        if (fromAct.getBalance() < money) {
            throw new MoneyNotEnoughException("余额不足,转账失败!");
        }

        // 3. 如果转出账户余额充足,更新转出账户余额(update)
        fromAct.setBalance(fromAct.getBalance() - money);
        int count = accountDaoImpl.updateByActno(fromact);

        // 4. 更新转入账户余额(update)
        Account toAct = accountDaoImpl.selectByActno(toActno);
        toAct.setBalance(toAct.getBalance() + money);
        count += accountDaoImpl.updateByActno(toAct);
        if(2 != count){
            throw new TransferException("未知错误,转账失败!");
        }
        // 提交事务
        sqlSession.commit();
        // 关闭资源
        SqlSessionUtil.close(sqlSession);
    }
}
  • 运行前数据库中的数据:

[记录学习]自学动力节点老杜MyBatis笔记_02_第18张图片

  • 运行结果:

[记录学习]自学动力节点老杜MyBatis笔记_02_第19张图片

[记录学习]自学动力节点老杜MyBatis笔记_02_第20张图片

查看数据库数据没有变化:

[记录学习]自学动力节点老杜MyBatis笔记_02_第21张图片

去除异常查看功能是否可以实现:

[记录学习]自学动力节点老杜MyBatis笔记_02_第22张图片

[记录学习]自学动力节点老杜MyBatis笔记_02_第23张图片

在这里插入图片描述

  • 余额不足的情况:

[记录学习]自学动力节点老杜MyBatis笔记_02_第24张图片

[记录学习]自学动力节点老杜MyBatis笔记_02_第25张图片

[记录学习]自学动力节点老杜MyBatis笔记_02_第26张图片

6.5 分析当前程序存在的问题

  • 来看一下DaoImpl的代码
package com.powernode.bank.dao.impl;

import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

public class AccountDaoImpl implements AccountDao {
    public Account selectByActno(String actno) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        return (Account) sqlSession.selectOne("account.selectByActno", actno);
    }

    public int updateByActno(Account act) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        return sqlSession.update("account.updateByActno", act);
    }
}
  • 我们不难发现,这个dao实现类中的方法代码很固定,基本上就是一行代码,通过SqlSession对象调用 insert、delete、update、select等方法,这个类中的方法没有任何业务逻辑,既然是这样,这个类我们能不能动态的生成,以后可以不写这个类吗?

    答案:可以。

七、使用javassist生成类

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学 系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用 Javassist对字节码操作为JBoss实现动态"AOP"框架

7.1 Javassist的使用

  • 我们要使用javassist,首先要引入它的依赖
<dependency>
    <groupId>org.javassistgroupId>
    <artifactId>javassistartifactId>
    <version>3.21.0-GAversion>
dependency>
  • 样例代码:
package com.powernode.javassist.test;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class JavassistTest {
    public static void main(String[] args) throws Exception{
        // 获取类池
        ClassPool pool = ClassPool.getDefault();
        // 创建类
        CtClass ctClass = pool.makeClass("com.powernode.javassist.Test");
        // 创建方法
        // 1.返回值类型  2.方法名  3.形式参数列表  4.所属类
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute", new CtClass[]{}, ctClass);
        // 设置方法的修饰符列表
        ctMethod.setModifiers(Modifier.PUBLIC);
        // 设置方法体
        ctMethod.setBody("{System.out.println(\"Hello world!\");}");
        // 给类添加方法
        ctClass.addMethod(ctMethod);
        // 调用方法
        Class aClass = ctClass.toClass();
        Object o = aClass.newInstance();
        Method execute = aClass.getDeclaredMethod("execute");
        execute.invoke(o);
    }
}
  • 运行要注意:加两个参数,要不然会有异常。(本人jdk1.8没配置未出现异常)

    • –add-opens java.base/java.lang=ALL-UNNAMED
    • –add-opens java.base/sun.net.util=ALL-UNNAMED

[记录学习]自学动力节点老杜MyBatis笔记_02_第27张图片

  • 执行结果:

[记录学习]自学动力节点老杜MyBatis笔记_02_第28张图片

  • 课堂测试:

    • dao层内容:
    package com.powernode.dao;
    
    public interface AccountDao {
        void delete();
        int insert(String actno);
        int update(String actno, Double balance);
        String selectByActno(String actno);
    }
    
    • 测试一:testGenerateFirstClass
    @Test
    public void testGenerateFirstClass() throws Exception{
        // 获取类池,这个类池就是用来给我生成class的
        ClassPool pool = ClassPool.getDefault();
        // 制造类(需要告诉javassist,类名是啥)
        CtClass ctClass = pool.makeClass("com.powernode.dao.impl.AccountDaoImpl");
        // 制造方法
        String methodCode = "public void insert(){System.out.println(123);}";
        CtMethod ctMethod = CtMethod.make(methodCode, ctClass);
        // 将方法添加到类中
        ctClass.addMethod(ctMethod);
        // 在内存中生成class
        ctClass.toClass();
    
    
        // 类加载到JVM当中,返回AccountDaoImpl类的字节码
        Class<?> clazz = Class.forName("com.powernode.dao.impl.AccountDaoImpl");
        // 创建对象
        Object obj = clazz.newInstance();
        // 获取AccountDaoImpl中的insert方法
        Method insertMethod = clazz.getDeclaredMethod("insert");
        // 调用方法insert
        insertMethod.invoke(obj);
    }
    

    运行结果:

[记录学习]自学动力节点老杜MyBatis笔记_02_第29张图片

  • 测试二:testGenerateImpl
@Test
public void testGenerateImpl() throws Exception{
    // 获取类池
    ClassPool pool = ClassPool.getDefault();
    // 制造类
    CtClass ctClass = pool.makeClass("com.powernode.dao.impl.AccountDaoImpl");
    // 制造接口
    CtClass ctInterface = pool.makeInterface("com.powernode.dao.AccountDao");
    // 添加接口到类中
    ctClass.addInterface(ctInterface); // AccountDaoImpl implements AccountDao
    // 实现接口中的方法
    // 制造方法
    CtMethod ctMethod = CtMethod.make("public void delete(){System.out.println(\"hello delete!\");}", ctClass);
    // 将方法添加到类中
    ctClass.addMethod(ctMethod);
    // 在内存中生成类,同时将生成的类加载到JVM当中。
    Class<?> clazz = ctClass.toClass();
    AccountDao accountDao = (AccountDao)clazz.newInstance();
    accountDao.delete();
}

执行结果:

[记录学习]自学动力节点老杜MyBatis笔记_02_第30张图片

  • 测试三:testGenerateAccountDaoImpl
@Test
public void testGenerateAccountDaoImpl() throws Exception{
    // 获取类池
    ClassPool pool = ClassPool.getDefault();
    // 制造类
    CtClass ctClass = pool.makeClass("com.powernode.impl.AccountDaoImpl");
    // 制造接口
    CtClass ctInterface = pool.makeInterface("com.powernode.dao.AccountDao");
    // 实现接口
    ctClass.addInterface(ctInterface);
    // 实现接口中所有的方法
    // 获取接口中所有的方法
    Method[] methods = AccountDao.class.getDeclaredMethods();
    Arrays.stream(methods).forEach(method -> {
        // method是接口中的抽象方法
        // 把method抽象方法给实现了。
        try {
            // public void delete(){}
            // public int update(String actno, Double balance){}
            StringBuilder methodCode = new StringBuilder();
            methodCode.append("public "); // 追加修饰符列表
            methodCode.append(method.getReturnType().getName()); // 追加返回值类型
            methodCode.append(" ");
            methodCode.append(method.getName()); //追加方法名
            methodCode.append("(");
            // 拼接参数 String actno, Double balance
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> parameterType = parameterTypes[i];
                methodCode.append(parameterType.getName());
                methodCode.append(" ");
                methodCode.append("arg" + i);
                if(i != parameterTypes.length - 1){
                    methodCode.append(",");
                }
            }
            methodCode.append("){System.out.println(11111); ");
            // 动态的添加return语句
            String returnTypeSimpleName = method.getReturnType().getSimpleName();
            if ("void".equals(returnTypeSimpleName)) {

            }else if("int".equals(returnTypeSimpleName)){
                methodCode.append("return 1;");
            }else if("String".equals(returnTypeSimpleName)){
                methodCode.append("return \"hello\";");
            }
            methodCode.append("}");
            System.out.println(methodCode);
            CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
            ctClass.addMethod(ctMethod);
        } catch (Exception e) {
            e.printStackTrace();
        }

    });

    // 在内存中生成class,并且加载到JVM当中
    Class<?> clazz = ctClass.toClass();
    // 创建对象
    AccountDao accountDao = (AccountDao) clazz.newInstance();
    // 调用方法
    accountDao.insert("aaaaa");
    accountDao.delete();
    accountDao.update("aaaa", 1000.0);
    accountDao.selectByActno("aaaa");
}

执行结果:

[记录学习]自学动力节点老杜MyBatis笔记_02_第31张图片

7.2 使用Javassist生成Proxy类(module以第六章为基础)

  • GenerateDaoProxy工具类
package com.powernode.bank.utils;

import com.powernode.bank.dao.AccountDao;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.SqlSession;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 工具类:可以动态生成DAO实现类。(或者说可以动态生成DAO的代理类)
 * 注意!!!!非常重要!!!
 * 凡是使用GenerateDaoProxy的,SQLMapper.xml映射文件中的namespace必须是dao接口的全名,id必须是dao接口中的方法名。
 *
 * @author ShiningSong
 * @version 1.0
 * @since 1.0
 */
public class GenerateDaoProxy {

    /**
     * 生成dao接口实现类,并且将累的对象创建出来并返回
     *
     * @param sqlSession
     * @param daoInterface dao接口
     * @return dao接口实现类的实例化对象
     */
    public static Object generate(SqlSession sqlSession, Class daoInterface) {
        // 类池
        ClassPool pool = ClassPool.getDefault();
        // 制造类(com.powernode.bank.dao.AccountDao --> com.powernode.bank.dao.AccountDaoProxy)
        CtClass ctClass = pool.makeClass(daoInterface.getName() + "Proxy");
        // 制造接口
        CtClass ctInterface = pool.makeInterface(daoInterface.getName());
        // 实现接口
        ctClass.addInterface(ctInterface);
        // 实现接口的所有
        Method[] methods = daoInterface.getDeclaredMethods();
        Arrays.stream(methods).forEach(method -> {
            // method是这个接口中的抽象方法
            // 将method这个抽象方法进行实现
            try {
                // Account selectByActno(String actno);
                // public Account selectByActno(String actno){代码};
                StringBuilder methodCode = new StringBuilder();
                methodCode.append("public ");
                methodCode.append(method.getReturnType().getName());
                methodCode.append(" ");
                methodCode.append(method.getName());
                methodCode.append("(");
                // 需要方法的形式参数列表
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int i = 0; i < parameterTypes.length; i++) {
                    methodCode.append(parameterTypes[i].getName());
                    methodCode.append(" ");
                    methodCode.append("arg" + i);
                    if (i < parameterTypes.length-1) {
                        methodCode.append(", ");
                    }
                }
                methodCode.append("){\n");
                // 需要方法体当中的代码
                methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();\n");
                // 需要知道是什么类型的sql语句
                // sql语句的id是框架使用者提供的,具有多变性,对于框架的开发人员来说,是不知道具体内容的。
                // 既然框架开发者不知道sqlId,怎么办呢?mybatis框架的开发者于是就出台了一个规定:凡是使用GenerateDaoProxy机制的,sqlId都不能随便写。
                // namespace必须是dao接口的全限定名称,id必须是dao接口中的方法名。
                String sqlId = daoInterface.getName() + "." + method.getName();
//                System.out.println(sqlId);
                SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
                switch (sqlCommandType){
                    case INSERT: break;
                    case DELETE: break;
                    case UPDATE: {
                        methodCode.append("return sqlSession.update(\""+sqlId+"\", arg0);");
                    }break;
                    case SELECT: {
                        methodCode.append("return ("+method.getReturnType().getName()+")sqlSession.selectOne(\""+sqlId+"\", arg0);");
                    }break;
                }
                methodCode.append("\n}");
            System.out.println(methodCode);
                CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
                ctClass.addMethod(ctMethod);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        // 创建对象
        Object obj = null;
        try {
            Class<?> clazz = ctClass.toClass();
            obj = clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }

    public static void main(String[] args) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        Object obj = GenerateDaoProxy.generate(sqlSession, AccountDao.class);
        System.out.println(obj.toString());
    }
}
  • 修改AccountMapper.xml文件:namespace必须是dao接口的全限定名称,id必须是dao接口中的方法名:

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

<mapper namespace="com.powernode.bank.dao.AccountDao">
    <select id="selectByActno" resultType="com.powernode.bank.pojo.Account">
        select * from t_act where actno=#{actno};
    select>

    <update id="updateByActno">
        update t_act set balance=#{balance} where actno=#{actno}
    update>
mapper>
  • 修改service类在这里插入图片描述
    中获取dao对象的代码:

  • 注:本人在采用该方法时遇见以下问题,在寻找许多解决方案后仍然无果,不知道是不是版本问题导致的,但由于不是核心问题(因为采用mybatis提供的getMapper()方法可以正常使用),因此打算暂时搁置下来,等将来再常识解决。

    • 启动过程没有任何问题
    • 当点击转账按钮时出现以下报错:

    网页端:

[记录学习]自学动力节点老杜MyBatis笔记_02_第32张图片

服务器端:

[记录学习]自学动力节点老杜MyBatis笔记_02_第33张图片

在这里插入图片描述

[记录学习]自学动力节点老杜MyBatis笔记_02_第34张图片

正常情况下继续:

  • 启动服务器:启动过程中显示,tomcat服务器自动添加了以下的两个运行参数。所以不需要再单独配置。

[记录学习]自学动力节点老杜MyBatis笔记_02_第35张图片

  • 测试前数据:

[记录学习]自学动力节点老杜MyBatis笔记_02_第36张图片

  • 测试结果:

[记录学习]自学动力节点老杜MyBatis笔记_02_第37张图片

[记录学习]自学动力节点老杜MyBatis笔记_02_第38张图片

在这里插入图片描述

八、MyBatis中接口代理机制及使用

以上所讲内容mybatis内部已经实现了。直接调用以下代码即可获取dao接口的代理类:

// 在mybatis当中,mybatis提供了相关的机制。也可以动态为我们生成dao接口的实现类。(代理类:dao接口的代理)
// mybatis当中实际上采用了代理模式。在内存中生成dao接口的代理类,然后创建代理类的实例。
// 使用mybatis的这种代理机制的前提:SqlMapper.xml文件中namespace必须是dao接口的全限定名称,id必须是dao接口中的方法名。
// 怎么用?代码怎么写?AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
private AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class);

[记录学习]自学动力节点老杜MyBatis笔记_02_第39张图片

  • 测试前数据:

[记录学习]自学动力节点老杜MyBatis笔记_02_第40张图片

  • 测试结果:

[记录学习]自学动力节点老杜MyBatis笔记_02_第41张图片

[记录学习]自学动力节点老杜MyBatis笔记_02_第42张图片

[记录学习]自学动力节点老杜MyBatis笔记_02_第43张图片

利用MyBatis中接口代理机制完成crud

  • 建包:

    • com.powernode.mybatis.utils
    • com.powernode.mybatis.pojo
    • com.powernode.mybatis.mapper
  • 引入类:

    • com.powernode.mybatis.utils.SqlSessionUtil.java
    • com.powernode.mybatis.pojo.Car.java
    • com.powernode.mybatis.mapper.CarMapper.java
    package com.powernode.mybatis.mapper;
    // 包名也有叫做:com.powernode.mybatis.dao
    // mapper包就是dao包。
    
    import com.powernode.mybatis.pojo.Car;
    
    import java.util.List;
    
    /**
     * CarMapper类,提供crud方法接口
     * 一般使用mybatis的话,一般不叫做XXXDao了。一般都是XXXMapper。
     * @author ShiningSong
     * @version 1.0
     * @since 1.0
     */
    public interface CarMapper {
    
        /**
         * 新增汽车信息
         * @param car
         * @return
         */
        int insert(Car car);
    
        /**
         * 根据id删除汽车信息
         * @param id
         * @return
         */
        int deleteById(Long id);
    
        /**
         * 根据id更新汽车信息
         * @param car
         * @return
         */
        int updateById(Car car);
    
        /**
         * 根据id查询汽车信息
         * @param id
         * @return
         */
        Car selectById(Long id);
    
        /**
         * 查询所有汽车信息
         * @return
         */
        List<Car> selectAll();
    }
    
  • 引入配置文件:

    • mybatis-config.xml
    • logback-test.xml
    • jdbc.properties
    • CarMapper.xml
    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.powernode.mybatis.mapper.CarMapper">
    
        <insert id="insert">
            insert into t_car values(null, #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType})
        insert>
        
        <delete id="deleteById">
            delete from t_car where id=#{id}
        delete>
    
        <update id="updateById">
            update t_car
            set car_num=#{carNum},
                brand=#{brand},
                guide_price=#{guidePrice},
                produce_time=#{produceTime},
                car_type=#{carType}
            where id=#{id}
        update>
    
        <select id="selectById" resultType="com.powernode.mybatis.pojo.Car">
            select
                id,
                car_num as carNum,
                brand,
                guide_price as guidePrice,
                produce_time as produceTime,
                car_type as carType
            from t_car
            where id=#{id}
        select>
    
        <select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
            select
                id,
                car_num as carNum,
                brand,
                guide_price as guidePrice,
                produce_time as produceTime,
                car_type as carType
            from t_car
        select>
        
    mapper>
    
  • 测试类:

package com.powernode.mybatis.test;

import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class CarCRUDTest {

    @Test
    public void testSelectAll(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectAll();
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }

    @Test
    public void testSelectById(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = mapper.selectById(15L);
        System.out.println(car);
        sqlSession.close();
    }

    @Test
    public void testUpdateById(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(15L, "7777", "布加迪威龙", 1000.0, "2015-01-19", "燃油车");
        int count = mapper.updateById(car);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void testDeleteById(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        int count = mapper.deleteById(14L);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void testInsert(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        // 面向接口获取接口的代理对象
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(null, "6666", "奔驰600", 60.0, "2011-12-03", "燃油车");
        int count = mapper.insert(car);
        System.out.println("插入了几条数据:" + count);
        sqlSession.commit();
        sqlSession.close();
    }
}

九、MyBatis小技巧

9.1 #{}和${}

  • #{}:先编译sql语句,再给占位符传值,底层是PreparedStatement实现。可以防止sql注入,比较常用。

  • ${}:先进行sql语句拼接,然后再编译sql语句,底层是Statement实现。存在sql注入现象。只有在需要 进行sql语句关键字拼接的情况下才会用到。

  • 需求:根据car_type查询汽车

  • 模块名:mybatis-005-antic

使用#{}

  • 依赖:

    • pom.xml
      • mybatis
      • mysql-connector-java
      • logback-classic
      • junit
    • jdbc.properites
    • logback-test.xml
    • com.powernode.mybatis.utils.SqlSessionUtil.java
    • com.powernode.mybatis.pojo.Car.java
    • com.powernode.mybatis.mapper.CarMapper.java接口
    package com.powernode.mybatis.mapper;
    
    import com.powernode.mybatis.pojo.Car;
    import java.util.List;
    
    /**
     * Car的sql映射对象
     * @author ShiningSong
     * @version 1.0
     * @since 1.0
     */
    public interface CarMapper {
        /**
         * 根据car_num获取Car
         * @param CarType
         * @return
         */
        List<Car> selectByCarType(String CarType);
    }
    
    • CarMapper.xml
    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.powernode.mybatis.mapper.CarMapper">
        <select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">
            select
                id,
                car_num as carNum,
                brand,
                guide_price as guidePrice,
                produce_time as produceTime,
                car_type as carType
            from t_car
            where car_type=#{carType}
        select>
    mapper>
    
    • 测试程序
    package com.powernode.mybatis.test;
    
    import com.powernode.mybatis.mapper.CarMapper;
    import com.powernode.mybatis.pojo.Car;
    import com.powernode.mybatis.utils.SqlSessionUtil;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    import java.util.List;
    
    public class CarMapperTest {
    
        @Test
        public void testSelectByCarType(){
            SqlSession sqlSession = SqlSessionUtil.openSession();
            CarMapper mapper = sqlSession.getMapper(CarMapper.class);
            List<Car> cars = mapper.selectByCarType("燃油车");
            cars.forEach(car -> System.out.println(car));
            sqlSession.close();
        }
    }
    
  • 执行结果:

在这里插入图片描述

  • 通过执行可以清楚的看到,sql语句中是带有 ? 的,这个 ? 就是大家在JDBC中所学的占位符,专门用来 接收值的。
  • 把“燃油车”以String类型的值,传递给 ?
  • 这就是 #{},它会先进行sql语句的预编译,然后再给占位符传值

使用${}

  • 同样的需求,我们使用${}来完成

  • CarMapper.xml文件修改如下:

    
    
    • 再次运行测试程序:

在这里插入图片描述

  • 出现异常了,这是为什么呢?看看生成的sql语句:

在这里插入图片描述

  • ${} 是先进行sql语句的拼接,然后再编译,出现语法错误是正常的,因为 燃油车 是一个字符串,在sql语句中应该添加单引号

  • 修改CarMapper.xml

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.powernode.mybatis.mapper.CarMapper">
        <select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">
            select
                id,
                car_num as carNum,
                brand,
                guide_price as guidePrice,
                produce_time as produceTime,
                car_type as carType
            from t_car
            where
                
                
                car_type='${carType}'
        select>
    mapper>
    
    • 再次运行测试程序:

在这里插入图片描述

  • 通过以上测试,可以看出,对于以上这种需求来说,还是建议使用 #{} 的方式。

  • 原则:能用 #{} 就不用 ${}

什么情况下必须使用${}

  • 当需要进行sql语句关键字拼接的时候。必须使用${}

  • 需求:通过向sql语句中注入asc或desc关键字,来完成数据的升序或降序排列。

  • 先使用#{}尝试:

    CarMappe接口:

    /**
    * 查询所有的Car
    * @param ascOrDesc asc或者desc
    * @return
    */
    List<Car> selectAllOrderByCarNum(String ascOrDesc);
    

    CarMapper.xml

    <select id="selectAllOrderByCarNum" resultType="com.powernode.mybatis.pojo.Car">
        select
        id,
        car_num as carNum,
        brand,
        guide_price as guidePrice,
        produce_time as produceTime,
        car_type as carType
        from t_car
        order by car_num #{key}
    select>
    

    测试程序:

    @Test
    public void testSelectAllOrderByCarNum(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectAllOrderByCarNum("desc");
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }
    
    • 运行结果:

[记录学习]自学动力节点老杜MyBatis笔记_02_第44张图片

报错的原因是sql语句不合法,因为采用这种方式传值,最终sql语句会是这样:

select 
	id, car_num as carNum, brand, 
	guide_price as guidePrice, produce_time as produceTime, car_type as carType 
from t_car 
order by car_num 'desc'

desc是一个关键字,不能带单引号的,所以在进行sql语句关键字拼接的时候,必须使用${}

  • 使用${} 改造

    CarMapper.xml

    <select id="selectAllOrderByCarNum" resultType="com.powernode.mybatis.pojo.Car">
        select
        id,
        car_num as carNum,
        brand,
        guide_price as guidePrice,
        produce_time as produceTime,
        car_type as carType
        from t_car
        order by car_num ${key}
    select>
    

    再次测试结果:

在这里插入图片描述

拼接表名

  • 业务背景:实际开发中,有的表数据量非常庞大,可能会采用分表方式进行存储,比如每天生成一张表,表的名字与日期挂钩,例如:2022年8月1日生成的表:t_user20220108。2000年1月1日生成的表:t_user20000101。此时前端在进行查询的时候会提交一个具体的日期,比如前端提交的日期为: 2000年1月1日,那么后端就会根据这个日期动态拼接表名为:t_user20000101。有了这个表名之后,将 表名拼接到sql语句当中,返回查询结果。那么大家思考一下,拼接表名到sql语句当中应该使用#{} 还是 ${} 呢?

    • 使用#{}会是这样:select * from ‘t_car’

    • 使用​${}会是这样:select * from t_car

      CarMapper.xml

      <select id="selectAllByTableName" resultType="com.powernode.mybatis.pojo.Car">
          select
          id,
          car_num as carNum,
          brand,
          guide_price as guidePrice,
          produce_time as produceTime,
          car_type as carType
          from ${tableName}
      select>
      

      CarMapper接口

      /**
      * 根据表名查询所有的Car
      * @param tableName
      * @return
      */
      List<Car> selectAllByTableName(String tableName);
      

      测试类:

      @Test
      public void testSelectAllByTableName(){
          SqlSession sqlSession = SqlSessionUtil.openSession();
          CarMapper mapper = sqlSession.getMapper(CarMapper.class);
          List<Car> cars = mapper.selectAllByTableName("t_car");
          cars.forEach(car -> System.out.println(car));
          sqlSession.close();
      }
      

      执行结果:

在这里插入图片描述

批量删除

  • 业务背景:一次删除多条记录。

  • 对应的sql语句:

    • delete from t_user where id = 1 or id = 2 or id = 3;
    • delete from t_user where id in(1, 2, 3);
  • 假设现在使用in的方式处理,前端传过来的字符串:1, 2, 3

  • 如果使用mybatis处理,应该使用#{} 还是 ${}

  • 使用#{} :delete from t_user where id in(‘1,2,3’)

    • 执行错误:1292 - Truncated incorrect DOUBLE value: ‘1,2,3’
  • 使用${} :delete from t_user where id in(1, 2, 3)

    CarMapper接口

    /**
    * 根据id批量删除
    * @param ids
    * @return
    */
    int deleteBatch(String ids);
    

    CarMapper.xml

    <delete id="deleteBatch">
        delete from t_car where id in (${ids})
    delete>
    

    测试类:

    @Test
    public void testDeleteBatch(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        int count = mapper.deleteBatch("11, 19, 36");
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }
    

    执行结果:

在这里插入图片描述

模糊查询

  • 需求:查询奔驰系列的汽车。【只要品牌brand中含有奔驰两个字的都查询出来。】

  • 使用${}

    CarMapper接口

    /**
    * 根据品牌进行模糊查询
    * @param likeBrand
    * @return
    */
    List<Car> selectLikeBrand(String likeBrand);
    

    CarMapper.xml

    <select id="selectLikeBrand" resultType="com.powernode.mybatis.pojo.Car">
        select
        id,
        car_num as carNum,
        brand,
        guide_price as guidePrice,
        produce_time as produceTime,
        car_type as carType
        from t_car
        where brand like '%${brand}%'
    select>
    

    测试类:

    @Test
    public void testSelectLikeBrand(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectLikeBrand("奔驰");
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }
    

    执行结果:

[记录学习]自学动力节点老杜MyBatis笔记_02_第45张图片

  • 使用#{}

    • 第一种:concat函数

      CarMapper.xml

      <select id="selectLikeBrand" resultType="com.powernode.mybatis.pojo.Car">
          select
          id,
          car_num as carNum,
          brand,
          guide_price as guidePrice,
          produce_time as produceTime,
          car_type as carType
          from t_car
          where
          
          brand like concat('%', #{brand}, '%')
      select>
      

      执行结果:

    在这里插入图片描述

    • 第二种:双引号方式

      CarMapper.xml

      <select id="selectLikeBrand" resultType="com.powernode.mybatis.pojo.Car">
          select
          id,
          car_num as carNum,
          brand,
          guide_price as guidePrice,
          produce_time as produceTime,
          car_type as carType
          from t_car
          where
          
          
          brand like "%"#{brand}"%"
      select>
      

      执行结果:

      [记录学习]自学动力节点老杜MyBatis笔记_02_第46张图片

9.2 typeAliases 别名

  • CarMapper.xml的配置信息中,resultType属性用来指定查询结果集的封装类型,这个名字太长,可以起别名吗?
    • 可以
  • 在mybatis-config.xml文件中使用typeAliases标签来起别名,包括两种方式:

第一种方式:typeAlias

<typeAliases>
    <typeAlias type="com.powernode.mybatis.pojo.Car" alias="Car"/>
typeAliases>
  • 首先要注意typeAliases标签的放置位置,如果不清楚的话,可以看看错误提示信息。
  • typeAliases标签中的typeAlias可以写多个。
  • typeAlias:
    • type属性:指定给哪个类起别名
    • alias属性:别名。
      • alias属性不是必须的,如果缺省的话,type属性指定的类型名的简类名作为别名。
      • alias是大小写不敏感的。也就是说假设alias=“Car”,再用的时候,可以CAR,也可以car, 也可以Car,都行。

第二种方式:package

  • 如果一个包下的类太多,每个类都要起别名,会导致typeAlias标签配置较多,所以mybatis用提供 package的配置方式,只需要指定包名,该包下的所有类都自动起别名,别名就是简类名。并且别名不区分大小写。

  • package也可以配置多个的。

    <typeAliases>
        
        <package name="com.powernode.mybatis.pojo"/>
    typeAliases>
    

9.3 mappers

  • SQL映射文件的配置方式包括四种:
    • resource:从类路径中加载
    • url:从指定的全限定资源路径中加载
    • class:使用映射器接口实现类的完全限定类名
    • package:将包内的映射器接口实现全部注册为映射器

resource

  • 这种方式是从类路径中加载配置文件,所以这种方式要求SQL映射文件必须放在resources目录下或其子目录下。
<mappers>
    <mapper resource="CarMapper.xml"/>
mappers>

url

这种方式显然使用了绝对路径的方式,这种配置对SQL映射文件存放的位置没有要求,随意。

<mappers>
    <mapper url="file:///E:\ReLearn_MyBatis_Workspace\mybatis-006-antic\src\main\resources\CarMapper.xml"/>
mappers>

class

  • 如果使用这种方式必须满足以下条件:

    • SQL映射文件和mapper接口放在同一个目录下。
    • SQL映射文件的名字也必须和mapper接口名一致。
  • 思考:mapper标签的作用是指定SqlMapper.xml文件的路径,指定接口名有什么用呢?

    • 如果你class指定是:com.powernode.mybatis.mapper.CarMapper,那么mybatis框架会自动去com/powernode/mybatis/mapper目录下查找CarMapper.xml文件。
    • 注意:如果你采用这种方式,那么你必须保证CarMapper.xml文件和CarMapper接口必须在同一个目录下,并且名字一致。
  • 在resources目录下新建:com/powernode/mybatis/mapper

    • 这⾥千万要注意:不能这样新建 com.powernode.mybatis.dao
  • 将CarMapper.xml文件移动到mapper目录下

  • 修改mybatis-config.xml文件

<mappers>
    <mapper class="com.powernode.mybatis.mapper.CarMapper"/>
mappers>

package

  • 如果class较多,可以使用这种package的方式,但前提条件和class方式一样。
<mappers>
    <package name="com.powernode.mybatis.mapper"/>
mappers>

9.4 idea配置文件模板

mybatis-config.xml和SqlMapper.xml文件可以在IDEA中提前创建好模板,以后通过模板创建配置文 件。

[记录学习]自学动力节点老杜MyBatis笔记_02_第47张图片

9.5 插入数据时获取自动生成的主键

  • 前提是:主键是自动生成的。

  • 业务背景:一个用户有多个角色。

    [记录学习]自学动力节点老杜MyBatis笔记_02_第48张图片

    • 插入一条新的记录之后,自动生成了主键,而这个主键需要在其他表中使用时。
    • 插入一个用户数据的同时需要给该用户分配角色:需要将生成的用户的id插入到角色表的user_id字段上。
  • 第一种方式:可以先插入用户数据,再写一条查询语句获取id,然后再插入user_id字段。【比较麻烦】

  • 第二种方式:mybatis提供了一种方式更加便捷:

    CarMapper接口

    /**
    * 获取自动生成的主键
    * @param car
    */
    int insertGetGeneratedKeys(Car car);
    

    CarMapper.xml

    <insert id="insertGetGeneratedKeys" useGeneratedKeys="true" keyProperty="id">
        insert into t_car 
        values (null, #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType})
    insert>
    

    测试类:

    @Test
    public void testInsertGetGeneratedKeys(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(null, "8888", "劳斯莱斯幻影", 920.0, "2018-09-10", "燃油车");
        int count =  mapper.insertGetGeneratedKeys(car);
        System.out.println("插入了几条数据:" + count);
        sqlSession.commit();
        System.out.println("自动生成的CarId为:" + car.getId());
        sqlSession.close();
    }
    

    执行结果:

    在这里插入图片描述

课堂笔记

mybatis小技巧
1. #{}和${}的区别

#{}的执行结果:
[main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==>  Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = ?
[main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==> Parameters: 新能源(String)
[main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - <==      Total: 2

${}的执行结果:
[main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==>  Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = 新能源
[main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==> Parameters:

org.apache.ibatis.exceptions.PersistenceException:
### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: Unknown column '新能源' in 'where clause'
### The error may exist in CarMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: select             id,             car_num as carNum,             brand,             guide_price as guidePrice,             produce_time as produceTime,             car_type as carType         from             t_car         where             car_type = 新能源
### Cause: java.sql.SQLSyntaxErrorException: Unknown column '新能源' in 'where clause'

#{}和${}的区别:
    #{}: 底层使用PreparedStatement。特点:先进行SQL语句的编译,然后给SQL语句的占位符问号?传值。可以避免SQL注入的风险。
    ${}:底层使用Statement。特点:先进行SQL语句的拼接,然后再对SQL语句进行编译。存在SQL注入的风险。
    优先使用#{},这是原则。避免SQL注入的风险。

#{}的执行结果:
Preparing: select
                id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
           from t_car order by produce_time ?
Parameters: asc(String)

select
    id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
from t_car order by produce_time 'asc'

${}的执行结果:
Preparing:
    select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
    from t_car order by produce_time asc
Parameters:

如果需要SQL语句的关键字放到SQL语句中,只能使用${},因为#{}是以值的形式放到SQL语句当中的。

2. 向SQL语句当中拼接表名,就需要使用${}
    现实业务当中,可能会存在分表存储数据的情况。因为一张表存的话,数据量太大。查询效率比较低。
    可以将这些数据有规律的分表存储,这样在查询的时候效率就比较高。因为扫描的数据量变少了。
    日志表:专门存储日志信息的。如果t_log只有一张表,这张表中每一天都会产生很多log,慢慢的,这个表中数据会很多。
    怎么解决问题?
        可以每天生成一个新表。每张表以当天日期作为名称,例如:
            t_log_20220901
            t_log_20220902
            ....
    你想知道某一天的日志信息怎么办?
        假设今天是20220901,那么直接查:t_log_20220901的表即可。

3.批量删除:一次删除多条记录。
    批量删除的SQL语句有两种写法:
        第一种or:delete from t_car where id=1 or id=2 or id=3;
        第二种int:delete from t_car where id in(1,2,3);

    应该采用${}的方式:
        delete from t_car where id in(${ids});

4.模糊查询:like
    需求:根据汽车品牌进行模糊查询
        select * from t_car where brand like '%奔驰%';
        select * from t_car where brand like '%比亚迪%';

    第一种方案:
        '%${brand}%'
    第二种方案:concat函数,这个是mysql数据库当中的一个函数,专门进行字符串拼接
        concat('%',#{brand},'%')
    第三种方案:比较鸡肋了。可以不算。
        concat('%','${brand}','%')
    第四种方案:
        "%"#{brand}"%"

5. 关于MyBatis中别名机制:
    
        
        
        

        
        
        

        
        
    

    所有别名不区分大小写。
    namespace不能使用别名机制。
6. mybatis-config.xml文件中的mappers标签。
     要求类的根路径下必须有:CarMapper.xml
     要求在d:/下有CarMapper.xml文件
    

    mapper标签的属性可以有三个:
        resource:这种方式是从类的根路径下开始查找资源。采用这种方式的话,你的配置文件需要放到类路径当中才行。
        url: 这种方式是一种绝对路径的方式,这种方式不要求配置文件必须放到类路径当中,哪里都行,只要提供一个绝对路径就行。这种方式使用极少,因为移植性太差。
        class: 这个位置提供的是mapper接口的全限定接口名,必须带有包名的。
            思考:mapper标签的作用是指定SqlMapper.xml文件的路径,指定接口名有什么用呢?
                
                如果你class指定是:com.powernode.mybatis.mapper.CarMapper
                那么mybatis框架会自动去com/powernode/mybatis/mapper目录下查找CarMapper.xml文件。
            注意:也就是说:如果你采用这种方式,那么你必须保证CarMapper.xml文件和CarMapper接口必须在同一个目录下。并且名字一致。
            CarMapper接口-> CarMapper.xml
            LogMapper接口-> LogMapper.xml
            ....

    提醒!!!!!!!!!!!!!!!!!!!!!!!
        在IDEA的resources目录下新建多重目录的话,必须是这样创建:
            com/powernode/mybatis/mapper
        不能这样:
            com.powernode.mybatis.mapper

十、MyBatis参数处理

  • 模块名:mybatis-006-param

  • 表:t_stu

    [记录学习]自学动力节点老杜MyBatis笔记_02_第49张图片

  • 表中数据:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第50张图片

  • pojo类:

    package com.powernode.mybatis.pojo;
    
    import java.util.Date;
    
    /**
     * 学生信息封装类
     * @author ShiningSong
     * @version 1.0
     * @since 1.0
     */
    public class Student {
        private Long id;
        private String name;
        private Integer age;
        private Double height;
        private Character sex;
        private Date birth;
    
        @Override
        public String toString() {
            return "Student{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    ", height=" + height +
                    ", sex=" + sex +
                    ", birth=" + birth +
                    '}';
        }
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public Double getHeight() {
            return height;
        }
    
        public void setHeight(Double height) {
            this.height = height;
        }
    
        public Character getSex() {
            return sex;
        }
    
        public void setSex(Character sex) {
            this.sex = sex;
        }
    
        public Date getBirth() {
            return birth;
        }
    
        public void setBirth(Date birth) {
            this.birth = birth;
        }
    
        public Student() {
        }
    
        public Student(Long id, String name, Integer age, Double height, Character sex, Date birth) {
            this.id = id;
            this.name = name;
            this.age = age;
            this.height = height;
            this.sex = sex;
            this.birth = birth;
        }
    }
    

10.1 单个简单类型参数

简单类型包括:

  • byte short int long float double char
  • Byte Short Integer Long Float Double Character
  • String java.util.Date
  • java.sql.Date

需求:

  • 根据name查

  • 根据id查

  • 根据birth查

  • 根据sex查

    StudentMapper接口

    package com.powernode.mybatis.mapper;
    
    import com.powernode.mybatis.pojo.Student;
    
    import java.util.Date;
    import java.util.List;
    
    /**
     * 学生表映射接口
     * @author ShiningSong
     * @version 1.0
     * @since 1.0
     */
    public interface StudentMapper {
    
        /**
         * 根据学生名查询学生信息
         * @param name
         * @return
         */
        List<Student> selectByName(String name);
    
        /**
         * 根据id查询学生信息
         * @param id
         * @return
         */
        Student selectById(Long id);
    
        /**
         * 根据生日birth查找学生信息
         * @param birth
         * @return
         */
        List<Student> selectByBirth(Date birth);
    
        /**
         * 根据性别sex查询学生信息
         * @param sex
         * @return
         */
        List<Student> selectBySex(Character sex);
    }
    

    StudentMapper.xml

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.powernode.mybatis.mapper.StudentMapper">
    
        <select id="selectByName" resultType="Student">
            select * from t_stu where name=#{name}
        select>
    
        <select id="selectById" resultType="Student">
            select * from t_stu where id=#{id}
        select>
    
        <select id="selectByBirth" resultType="Student">
            select * from t_stu where birth=#{birth}
        select>
    
        <select id="selectBySex" resultType="Student">
            select * from t_stu where sex=#{sex}
        select>
    
    mapper>
    

    测试类:

    package com.powernode.mybatis.test;
    
    import com.powernode.mybatis.mapper.StudentMapper;
    import com.powernode.mybatis.pojo.Student;
    import com.powernode.mybatis.utils.SqlSessionUtil;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.List;
    
    
    /**
     * 测试类:测试学生映射方法
     */
    public class StudentMapperTest {
    
        @Test
        public void testSelectBySex(){
            SqlSession sqlSession = SqlSessionUtil.openSession();
            StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
            List<Student> students = mapper.selectBySex('男');
            students.forEach(student -> System.out.println(student));
            sqlSession.close();
        }
    
        @Test
        public void testSelectByBirth(){
            SqlSession sqlSession = SqlSessionUtil.openSession();
            StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
            try {
                List<Student> students = mapper.selectByBirth(new SimpleDateFormat("yyyy-MM-dd").parse("2022-08-16"));
                students.forEach(student -> System.out.println(student));
            } catch (ParseException e) {
                throw new RuntimeException(e);
            }
            sqlSession.close();
        }
    
        @Test
        public void testSelectById(){
            SqlSession sqlSession = SqlSessionUtil.openSession();
            StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
            Student student = mapper.selectById(2L);
            System.out.println(student);
            sqlSession.close();
        }
    
        @Test
        public void testSelectByName(){
            SqlSession sqlSession = SqlSessionUtil.openSession();
            StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
            List<Student> students = mapper.selectByName("张三");
            students.forEach(student -> System.out.println(student));
            sqlSession.close();
        }
    
    }
    

    执行结果正常。

  • 通过测试得知,简单类型对于mybatis来说都是可以自动类型识别的:

    • 也就是说对于mybatis来说,它是可以自动推断出ps.setXxxx()方法的。ps.setString()还是 ps.setInt()。它可以自动推断。
  • 其实SQL映射文件中的配置比较完整的写法是:

    <select id="selectByName" resultType="Student" parameterType="java.lang.String">
        select * from t_stu where name=#{name}
    select>
    

    其中sql语句中的javaType,jdbcType,以及select标签中的parameterType属性,都是用来帮助 mybatis进行类型确定的。不过这些配置多数是可以省略的。因为mybatis它有强大的自动类型推断机制。

    • javaType:可以省略
    • jdbcType:可以省略
    • parameterType:可以省略
  • 如果参数只有一个的话,#{} ⾥面的内容就随便写了。对于 ${} 来说,注意加单引号。

10.2 Map参数

需求:根据name和age查询

StudentMapper接口

/**
* 利用Map根据name和age查询学生信息
* @param paramMap
* @return
*/
List<Student> selectByParaMap(Map<String, Object> paramMap);

StudentMapper.xml

<select id="selectByParaMap" resultType="Student">
    select * from t_stu where name=#{nameKey} and age=#{ageKey}
select>

测试类:

@Test
public void testSelectByParaMap(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    Map<String, Object> map = new HashMap<>();
    map.put("nameKey", "张三");
    map.put("ageKey", 20);
    List<Student> students = mapper.selectByParaMap(map);
    students.forEach(student -> System.out.println(student));
    sqlSession.close();
}

执行结果正常。

这种方式是手动封装Map集合,将每个条件以key和value的形式存放到集合中。然后在使用的时候通过#{map集合的key}来取值。

10.3 实体类参数

需求:插入一条Student数据

StudentMapper接口

/**
 * 插入学生信息
 * @param student
 * @return
 */
int insertByPOJO(Student student);

StudentMapper.xml

<insert id="insertByPOJO">
    insert into t_stu
    values (null, #{name}, #{age}, #{height}, #{birth}, #{sex})
insert>

测试类:

@Test
public void testInsertByPOJO(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    Student student = new Student(null, "李四", 18, 1.70, '男', new Date());
    int count = mapper.insertByPOJO(student);
    System.out.println(count);
    sqlSession.commit();
    sqlSession.close();
}

执行结果正常。

这⾥需要注意的是:#{} ⾥面写的是属性名字。这个属性名其本质上是:set/get方法名去掉set/get之后的名字。

10.4 多参数

需求:通过name和sex查询

StudentMapper接口

/**
 * 多参数查询,根据name和sex查询学生信息
 * @return
 */
List<Student> selectByMultiParam(String name, Character sex);

StudentMapper.xml

<select id="selectByMultiParam" resultType="Student">
    select * from t_stu
    where name=#{name} and sex=#{sex}
select>

测试类:

@Test
public void testSelectByMultiParam(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> students = mapper.selectByMultiParam("张三", '女');
    students.forEach(student -> System.out.println(student));
    sqlSession.close();
}

执行结果:

[记录学习]自学动力节点老杜MyBatis笔记_02_第51张图片

异常信息描述了:name参数找不到,可用的参数包括[arg1, arg0, param1, param2]

修改StudentMapper.xml配置文件:尝试使用[arg1, arg0, param1, param2]去参数

StudentMapper.xml

<select id="selectByMultiParam" resultType="Student">
    select * from t_stu
    where name=#{arg0} and sex=#{arg1}
select>

执行结果:

在这里插入图片描述

通过测试可以看到:

  • arg0 是第一个参数
  • param1是第一个参数
  • arg1 是第二个参数
  • param2是第二个参数

采用param1, param2, … 的方式也可以,甚至可以arg*和param*混用,但是要记住,arg*标记是从0开始,param*标记是从1开始

**实现原理:**实际上在mybatis底层会创建一个map集合,以arg0/param1为key,以方法上的参数为 value,例如以下代码:

Map<String, Object> map = new HashMap<>();
map.put("arg0", name);
map.put("arg1", sex);
map.put("param1", name);
map.put("param2", sex);
// 所以可以这样取值:#{arg0} #{arg1} #{param1} #{param2}
// 其本质就是#{map集合的key}

注意:使用mybatis3.4.2之前的版本时:要用#{0}和#{1}这种形式。

10.5 @Param注解(命名参数)

  • 可以不用arg0 arg1 param1 param2吗?这个map集合的key我们自定义可以吗?

    • 当然可以。使用 @Param注解即可。这样可以增强可读性。
  • 需求:根据name和age查询

    StudentMapper接口

    /**
     * 使用@Param注解,根据name和age查询学生信息
     * @return
     */
    List<Student> selectByParamAnnotation(@Param("name")String name, @Param("age")Integer age);
    

    StudentMapper.xml

    <select id="selectByParamAnnotation" resultType="Student">
        select * from t_stu
        where name=#{name} and age=#{age}
    select>
    

    测试类:

    @Test
    public void testSelectByParamAnnotation(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> students = mapper.selectByParamAnnotation("张三", 20);
        students.forEach(student -> System.out.println(student));
        sqlSession.close();
    }
    

    执行结果正常。

  • 核心:@Param(“这⾥填写的其实就是map集合的key”)

10.6 @Param源码分析

[记录学习]自学动力节点老杜MyBatis笔记_02_第52张图片
[记录学习]自学动力节点老杜MyBatis笔记_02_第53张图片

image-20221105113452255

十一、MyBatis查询语句专题

  • 模块名:mybatis-007-select
  • 打包方式:jar
  • 引入依赖:mysql驱动依赖、mybatis依赖、logback依赖、junit依赖。
  • 引入配置文件:jdbc.properties、mybatis-config.xml、logback.xml
  • 创建pojo类:Car
  • 创建Mapper接口:CarMapper
  • 创建Mapper接口对应的映射文件:com/powernode/mybatis/mapper/CarMapper.xml
  • 创建单元测试:CarMapperTest
  • 拷贝工具类:SqlSessionUtil

11.1 返回Car

  • 当查询的结果,有对应的实体类,并且查询结果只有一条时:

    CarMapper接口:

    package com.powernode.mybatis.mapper;
    
    import com.powernode.mybatis.pojo.Car;
    
    /**
     * 汽车表t_car映射接口
     * @author ShiningSong
     * @version 1.0
     * @since 1.0
     */
    public interface CarMapper {
    
        /**
         * 根据id查询车辆信息
         * @param id
         * @return
         */
        Car selectById(Long id);
    
    }
    

    CarMapper.xml

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.powernode.mybatis.mapper.CarMapper">
        <select id="selectById" resultType="Car">
            select
                id,
                car_num as carNum,
                brand,
                guide_price as guidePrice,
                produce_time as produceTime,
                car_type as carType
            from t_car
            where id=#{id}
        select>
    mapper>
    

    测试类:

    package com.powernode.mybatis.test;
    
    import com.powernode.mybatis.mapper.CarMapper;
    import com.powernode.mybatis.pojo.Car;
    import com.powernode.mybatis.utils.SqlSessionUtil;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    public class CarMapperTest {
    
        @Test
        public void testSelectById(){
            SqlSession sqlSession = SqlSessionUtil.openSession();
            CarMapper mapper = sqlSession.getMapper(CarMapper.class);
            Car car = mapper.selectById(2L);
            System.out.println(car);
            sqlSession.close();
        }
    }
    

    测试结果:

    在这里插入图片描述

  • 查询结果是一条的话可以使用List集合接收吗?

    • 当然可以。

    CarMapper接口

    /**
     * 根据id查询车辆信息放入List中
     * @param id
     * @return
     */
    List<Car> selectByIdToList(Long id);
    

    CarMapper.xml

    <select id="selectByIdToList" resultType="Car">
        select
        id,
        car_num as carNum,
        brand,
        guide_price as guidePrice,
        produce_time as produceTime,
        car_type as carType
        from t_car
        where id=#{id}
    select>
    

    测试类:

    @Test
    public void testSelectByIdToList(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectByIdToList(1L);
        System.out.println(cars);
        sqlSession.close();
    }
    

    执行结果:

    在这里插入图片描述

    11.2 返回List

    当查询的记录条数是多条的时候,必须使用集合接收。如果使用单个实体类接收会出现异常。

    CarMapper接口

    /**
     * 查询所有车辆信息
     * @return
     */
    List<Car> selectAll();
    

    CarMapper.xml

    <select id="selectAll" resultType="Car">
        select
        id,
        car_num as carNum,
        brand,
        guide_price as guidePrice,
        produce_time as produceTime,
        car_type as carType
        from t_car
    select>
    

    测试类:

    @Test
    public void testSelectAll(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectAll();
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第54张图片

  • 如果返回多条记录,采用单个实体类接收会怎样?

    CarMapper接口

    /**
     * 将查询结果返回单个POJO类
     * 如果查询结果超过一个会报异常
     * @return
     */
    Car selectAllToPOJO();
    

    CarMapper.xml

    <select id="selectAllToPOJO" resultType="Car">
        select
        id,
        car_num as carNum,
        brand,
        guide_price as guidePrice,
        produce_time as produceTime,
        car_type as carType
        from t_car
    select>
    

    测试类:

    @Test
    public void testSelectAllToPOJO(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = mapper.selectAllToPOJO();
        System.out.println(car);
        sqlSession.close();
    }
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第55张图片

    • 报错,因为查询结果不止一个对象,利用一个实体类对象是无法接收的

11.3 返回Map

  • 当返回的数据,没有合适的实体类对应的话,可以采用Map集合接收。

  • 字段名做key,字段值做value。 查询如果可以保证只有一条数据,则返回一个Map集合即可。

    [记录学习]自学动力节点老杜MyBatis笔记_02_第56张图片

    CarMapper接口

    /**
     * 通过id查询一条记录,返回Map集合
     * @param id
     * @return
     */
    Map<String, Object> selectByIdRetMap(Long id);
    

    resultMap=“map”,这是因为mybatis内置了很多别名。【参见mybatis开发手册】

    CarMapper.xml

    <select id="selectByIdRetMap" resultType="map">
        select
        id,
        car_num as carNum,
        brand,
        guide_price as guidePrice,
        produce_time as produceTime,
        car_type as carType
        from t_car
        where id=#{id}
    select>
    

    测试类:

    @Test
    public void testSelectByIdRetMap(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Map<String, Object> car = mapper.selectByIdRetMap(1L);
        System.out.println(car);
        sqlSession.close();
    }
    

    执行结果:

    在这里插入图片描述

  • 当然,如果返回一个Map集合,可以将Map集合放到List集合中吗?

    • 当然可以,这⾥就不再测试了。
  • 反过来,如果返回的不是一条记录,是多条记录的话,只采用单个Map集合接收,这样同样会出现之前的异常:TooManyResultsException

11.4 返回List

  • 查询结果条数大于等于1条数据,则可以返回一个存储Map集合的List集合。List等同于List

    [记录学习]自学动力节点老杜MyBatis笔记_02_第57张图片

    CarMapper接口

    /**
     * 查询所有车辆信息,返回一个List集合,List集合中存储的是保存车辆信息的Map集合
     * @return
     */
    List<Map<String, Object>> selectAllRetListMap();
    

    CarMapper.xml

    <select id="selectAllRetListMap" resultType="map">
        select
        id,
        car_num as carNum,
        brand,
        guide_price as guidePrice,
        produce_time as produceTime,
        car_type as carType
        from t_car
    select>
    

    测试类:

    @Test
    public void testSelectAllRetListMap(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Map<String, Object>> cars = mapper.selectAllRetListMap();
        System.out.println(cars);
        sqlSession.close();
    }
    

    执行结果:

    [
    {carType=燃油车, carNum=100, guidePrice=41.00, produceTime=2022-09-01, id=1, brand=宝马520Li}, 
    {carType=电车, carNum=101, guidePrice=54.00, produceTime=2022-08-01, id=2, brand=奔驰E300L}, 
    {carType=燃油车, carNum=1003, guidePrice=30.00, produceTime=2000-10-11, id=4, brand=丰田霸道},  
    ......, 
    {carType=燃油车, carNum=9999, guidePrice=920.00, produceTime=2018-09-10, id=40, brand=劳斯莱斯幻影}
    ]
    

11.5 返回Map

  • 拿Car的id做key,以后取出对应的Map集合时更方便。

  • 使用 @MapKey(“id”); 注解实现

    [记录学习]自学动力节点老杜MyBatis笔记_02_第58张图片

    CarMapper接口

    /**
     * 查询所有车辆信息,返回一个Map集合,
     * Map集合中存储的是保存车辆信息的Map集合,
     * 大Map的key是小map的id,使用@MapKey注解实现
     * @return
     */
    @MapKey("id")
    Map<Long, Map<String, Object>> selectAllRetMap();
    

    CarMapper.xml

    <select id="selectAllRetMap" resultType="map">
        select
        id,
        car_num as carNum,
        brand,
        guide_price as guidePrice,
        produce_time as produceTime,
        car_type as carType
        from t_car
    select>
    

    测试类:

    @Test
    public void testSelectAllRetMap(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Map<Long, Map<String, Object>> cars = mapper.selectAllRetMap();
        System.out.println(cars);
        sqlSession.close();
    }
    

    执行结果:

    {
    1={carType=燃油车, carNum=100, guidePrice=41.00, produceTime=2022-09-01, id=1, brand=宝马520Li}, 
    2={carType=电车, carNum=101, guidePrice=54.00, produceTime=2022-08-01, id=2, brand=奔驰E300L}, 
    4={carType=燃油车, carNum=1003, guidePrice=30.00, produceTime=2000-10-11, id=4, brand=丰田霸道}, 
    ......, 
    40={carType=燃油车, carNum=9999, guidePrice=920.00, produceTime=2018-09-10, id=40, brand=劳斯莱斯幻影}
    }
    

11.6 resultMap结果映射

查询结果的列名和java对象的属性名对应不上怎么办?

  • 第一种方式:as 给列起别名
  • 第二种方式:使用resultMap进行结果映射
  • 第三种方式:是否开启驼峰命名自动映射(配置settings)

使用resultMap进行结果映射

CarMapper接口

/**
 * 查询所有Car,使用resultMap进行结果映射
 * @return
 */
List<Car> selectAllByResultMap();

CarMapper.xml


<resultMap id="carResultMap" type="Car">
    
    <id property="id" column="id"/>
    <result property="carNum" column="car_num"/>
    
    
    <result property="brand" column="brand" javaType="String" jdbcType="VARCHAR"/>
    <result property="guidePrice" column="guide_price"/>
    <result property="produceTime" column="produce_time"/>
    <result property="carType" column="car_type"/>
resultMap>

<select id="selectAllByResultMap" resultMap="carResultMap">
    select * from t_car
select>

测试类:

@Test
public void testSelectAllByResultMap(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    List<Car> cars = mapper.selectAllByResultMap();
    cars.forEach(car -> System.out.println(car));
    sqlSession.close();
}

执行结果正常。

是否开启驼峰命名自动映射

  • 使用这种方式的前提是:属性名遵循Java的命名规范,数据库表的列名遵循SQL的命名规范。
  • Java命名规范:首字母小写,后面每个单词首字母大写,遵循驼峰命名方式。
  • SQL命名规范:全部小写,单词之间采用下划线分割。
  • 比如以下的对应关系:
实体类中的属性名 数据库表的列名
carNum car_num
guidePrice guide_price
produceTime produce_time
carType car_type
  • 如何启用该功能,在mybatis-config.xml文件中进行配置:

    
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    settings>
    

    CarMapper接口

    /**
     * 查询所有车辆信息,启用驼峰命名自动映射
     * @return
     */
    List<Car> selectAllByMapUnderscoreToCamelCase();
    

    CarMapper.xml

    <select id="selectAllByMapUnderscoreToCamelCase" resultType="Car">
        select * from t_car
    select>
    

    测试类:

    @Test
    public void testSelectAllByMapUnderscoreToCamelCase(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectAllByMapUnderscoreToCamelCase();
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }
    

    执行结果正常。

11.7 返回总记录条数

需求:查询总记录条数

CarMapper接口

/**
 * 获取总记录条数
 * @return
 */
Long selectTotal();

CarMapper.xml

<select id="selectTotal" resultType="long">
    select count(*) from t_car
select>

测试类:

@Test
public void testSelectTotal(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    Long count = mapper.selectTotal();
    System.out.println("总记录条数为:" + count);
    sqlSession.close();
}

执行结果:

在这里插入图片描述

十二、动态SQL

有的业务场景,也需要SQL语句进行动态拼接,例如:

[记录学习]自学动力节点老杜MyBatis笔记_02_第59张图片

delete from t_car where id in(1,2,3,4,5,6,......这⾥的值是动态的,根据用户选择的id不同,值是不同的);
  • 多条件查询

    [记录学习]自学动力节点老杜MyBatis笔记_02_第60张图片

    select * from t_car where brand like '丰⽥%' and guide_price > 30 and .....;
    
  • 创建模块:mybatis-008-dynamic-sql

  • 打包方式:jar

  • 引入依赖:mysql驱动依赖、mybatis依赖、junit依赖、logback依赖

  • pojo:com.powernode.mybatis.pojo.Car

  • mapper接口:com.powernode.mybatis.mapper.CarMapper

  • 引入配置文件:mybatis-config.xml、jdbc.properties、logback.xml

  • mapper配置文件:com/powernode/mybatis/mapper/CarMapper.xml

  • 编写测试类:com.powernode.mybatis.test.CarMapperTest

  • 拷贝工具类:SqlSessionUtil

12.1 if标签

  • 需求:多条件查询。

  • 可能的条件包括:品牌(brand)、指导价格(guide_price)、汽车类型(car_type)

    CarMapper接口

    package com.powernode.mybatis.mapper;
    
    import com.powernode.mybatis.pojo.Car;
    import org.apache.ibatis.annotations.Param;
    
    import java.util.List;
    
    /**
     * 汽车表映射接口
     * @author ShiningSong
     * @version 1.0
     * @since 1.0
     */
    public interface CarMapper {
    
        /**
         * 多条件查询车辆信息
         * 条件包括:品牌(brand)、指导价格(guide_price)、汽车类型(car_type)
         * @param brand
         * @param guidePrice
         * @param carType
         * @return
         */
        List<Car> selectByMultiCondition(@Param("brand")String brand, @Param("guidePrice")Double guidePrice, @Param("carType")String carType);
    }
    

    CarMapper.xml

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.powernode.mybatis.mapper.CarMapper">
    
        <select id="selectByMultiCondition" resultType="Car">
            select * from t_car where
            <if test="brand != null and brand != ''">
                brand like "%"#{brand}"%"
            if>
            <if test="guidePrice != null and guidePrice != ''">
                and guide_price >= #{guidePrice}
            if>
            <if test="carType != null and carType != ''">
                and car_type = #{carType}
            if>
    
        select>
    mapper>
    

    测试类:

    package com.powernode.mybatis.test;
    
    import com.powernode.mybatis.mapper.CarMapper;
    import com.powernode.mybatis.pojo.Car;
    import com.powernode.mybatis.utils.SqlSessionUtil;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    import java.util.List;
    
    public class CarMapperTest {
    
        @Test
        public void testSelectByMultiCondition(){
            SqlSession sqlSession = SqlSessionUtil.openSession();
            CarMapper mapper = sqlSession.getMapper(CarMapper.class);
            List<Car> cars = mapper.selectByMultiCondition("丰田", 10.0, "燃油车");
            cars.forEach(car -> System.out.println(car));
            sqlSession.close();
        }
    }
    

    执行结果:

    在这里插入图片描述

  • 如果第一个条件为空,剩下两个条件不为空,会是怎样呢?

    List<Car> cars = mapper.selectByMultiCondition(null, 10.0, "燃油车");
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第61张图片

    报错了,SQL语法有问题,where后面出现了and。这该怎么解决呢?

  • 可以where后面添加一个恒成⽴的条件。

    CarMapper.xml

    <select id="selectByMultiCondition" resultType="Car">
        
        select * from t_car where 1=1
        <if test="brand != null and brand != ''">
            brand like "%"#{brand}"%"
        if>
        <if test="guidePrice != null and guidePrice != ''">
            and guide_price >= #{guidePrice}
        if>
        <if test="carType != null and carType != ''">
            and car_type = #{carType}
        if>
    select>
    

    执行结果:

    在这里插入图片描述

  • 如果三个条件都是空,有影响吗?

    List<Car> cars = mapper.selectByMultiCondition("", null, "");
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第62张图片

  • 三个条件都不为空呢?

    • 第一个条件前也要加 and

    执行结果:

    在这里插入图片描述

12.2 where标签

where标签的作用:让where子句更加动态智能。

  • 所有条件都为空时,where标签保证不会生成where子句。
  • 自动去除某些条件前面多余的and或or。

继续使用if标签中的需求。

CarMapper接口

/**
 * 多条件查询车辆信息
 * 条件包括:品牌(brand)、指导价格(guide_price)、汽车类型(car_type)
 * 使用标签实现
 * @param brand
 * @param guidePrice
 * @param carType
 * @return
 */
List<Car> selectByMultiConditionWithWhere(@Param("brand")String brand, @Param("guidePrice")Double guidePrice, @Param("carType")String carType);

CarMapper.xml

<select id="selectByMultiConditionWithWhere" resultType="Car">
    select * from t_car
    <where>
        <if test="brand != null and brand != ''">
            and brand like "%"#{brand}"%"
        if>
        <if test="guidePrice != null and guidePrice != ''">
            and guide_price >= #{guidePrice}
        if>
        <if test="carType != null and carType != ''">
            and car_type = #{carType}
        if>
    where>
select>

测试类:

@Test
public void testSelectByMultiConditionWithWhere() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    List<Car> cars = mapper.selectByMultiConditionWithWhere("丰田", 10.0, "燃油车");
    cars.forEach(car -> System.out.println(car));
    sqlSession.close();
}

执行结果:

在这里插入图片描述

  • 如果所有条件都是空呢?

    测试类:

    List<Car> cars = mapper.selectByMultiCondition("", null, "");
    

    执行结果:

[记录学习]自学动力节点老杜MyBatis笔记_02_第63张图片

  • 它可以自动去掉前面多余的and,那可以自动去掉前面多余的or吗?

    测试类:

    List<Car> cars = mapper.selectByMultiConditionWithWhere("丰田", 10.0, "燃油车");
    

    CarMapper.xml

    <select id="selectByMultiConditionWithWhere" resultType="Car">
        select * from t_car
        <where>
            <if test="brand != null and brand != ''">
                or brand like "%"#{brand}"%"
            if>
            <if test="guidePrice != null and guidePrice != ''">
                and guide_price >= #{guidePrice}
            if>
            <if test="carType != null and carType != ''">
                and car_type = #{carType}
            if>
        where>
    select>
    

    测试结果:

    在这里插入图片描述

  • 它可以自动去掉前面多余的and,那可以自动去掉后面多余的and吗?

    测试类:

    // 设置最后一个参数为空
    List<Car> cars = mapper.selectByMultiConditionWithWhere("丰田", 10.0, "");
    

    CarMapper.xml

    <select id="selectByMultiConditionWithWhere" resultType="Car">
        select * from t_car
        <where>
            <if test="brand != null and brand != ''">
                brand like "%"#{brand}"%" and
            if>
            <if test="guidePrice != null and guidePrice != ''">
                guide_price >= #{guidePrice} and
            if>
            <if test="carType != null and carType != ''">
                car_type = #{carType}
            if>
        where>
    select>
    

    测试结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第64张图片

    很显然,后面多余的and是不会被去除的。

12.3 trim标签

trim标签的属性:

  • prefix:在trim标签中的语句前添加内容
  • suffix:在trim标签中的语句后添加内容
  • prefixOverrides:前缀覆盖掉(去掉)
  • suffixOverrides:后缀覆盖掉(去掉)

CarMapper接口

/**
 * 多条件查询车辆信息
 * 条件包括:品牌(brand)、指导价格(guide_price)、汽车类型(car_type)
 * 使用标签实现
 * @param brand
 * @param guidePrice
 * @param carType
 * @return
 */
List<Car> selectByMultiConditionWithTrim(@Param("brand")String brand, @Param("guidePrice")Double guidePrice, @Param("carType")String carType);

CarMapper.xml

<select id="selectByMultiConditionWithTrim" resultType="Car">
    select * from t_car
    <trim prefix="where" suffixOverrides="and|or">
        <if test="brand != null and brand != ''">
            brand like "%"#{brand}"%" and
        if>
        <if test="guidePrice != null and guidePrice != ''">
            guide_price >= #{guidePrice} and
        if>
        <if test="carType != null and carType != ''">
            car_type = #{carType}
        if>
    trim>
select>

测试类:

@Test
public void testSelectByMultiConditionWithTrim() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    List<Car> cars = mapper.selectByMultiConditionWithTrim("丰田", 10.0, "");
    cars.forEach(car -> System.out.println(car));
    sqlSession.close();
}

测试结果:

在这里插入图片描述

  • 如果所有条件为空,where会被加上吗?

    List<Car> cars = mapper.selectByMultiConditionWithTrim("", null, "");
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第65张图片

12.4 set标签

  • 主要使用在update语句当中,用来生成set关键字,同时去掉最后多余的“,”

  • 比如我们只更新提交的不为空的字段,如果提交的数据是空或者"",那么这个字段我们将不更新。

    CarMapper接口

    /**
     * 使用标签更新车辆信息
     * @param car
     * @return
     */
    int updateWithSet(Car car);
    

    CarMapper.xml

    <update id="updateWithSet">
        update t_car
        <set>
            <if test="carNum != null and carNum != ''">
                car_num = #{carNum},
            if>
            <if test="brand != null and brand != ''">
                brand = #{brand},
            if>
            <if test="guidePrice != null and guidePrice != ''">
                guide_price = #{guidePrice},
            if>
            <if test="produceTime != null and produceTime != ''">
                produce_time = #{produceTime},
            if>
            <if test="carType != null and carType != ''">
                car_type = #{carType},
            if>
        set>
        where id = #{id}
    update>
    

    测试类:

    @Test
    public void testUpdateWithSet(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(12L, "1010", "特斯拉model3", 26.0, "", null);
        int count = mapper.updateWithSet(car);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }
    

    测试结果:

    在这里插入图片描述

12.5 choose when otherwise

这三个标签是在一起使用的:

<choose>
    <when>when>
    <when>when>
    <when>when>
    <otherwise>otherwise>
choose>

等同于:

if(){
    
}else if(){
    
}else if(){
    
}else if(){
    
}else{
    
}

只有一个分支会被选择!!!!

  • 需求:先根据品牌查询,如果没有提供品牌,再根据指导价格查询,如果没有提供指导价格,就根据生 产日期查询。

    CarMapper接口

    /**
     * 使用标签查询车辆信息
     * 先根据品牌查询,如果没有提供品牌,再根据指导价格查询,如果没有提供指导价格,就根据生产日期查询。
     * @param brand
     * @param guidePrice
     * @param produceTime
     * @return
     */
    List<Car> selectByChoose(@Param("brand")String brand, @Param("guidePrice")Double guidePrice, @Param("produceTime")String produceTime);
    

    CarMapper.xml

    <select id="selectByChoose" resultType="Car">
        select * from t_car
        <where>
            <choose>
                <when test="brand != null and brand != ''">
                    brand like "%"#{brand}"%"
                when>
                <when test="guidePrice != null and guidePrice != ''">
                    guide_price >= #{guidePrice}
                when>
                <otherwise>
                    produce_time >= #{produceTime}
                otherwise>
            choose>
        where>
    select>
    

    测试类:

    @Test
    public void testSelectByChoose(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectByChoose("丰田霸道", 20.0, "2000-10-10");
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }
    

    执行结果:

    在这里插入图片描述

    测试类:

    List<Car> cars = mapper.selectByChoose("", 20.0, "2000-10-10");
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第66张图片

    测试类:

    List<Car> cars = mapper.selectByChoose("", null, "2000-10-10");
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第67张图片

    测试类:

    List<Car> cars = mapper.selectByChoose("", null, "");
    

    执行结果:

    在这里插入图片描述

12.6 foreach标签

  • 循环数组或集合,动态生成sql,比如这样的SQL:

    • 批量删除:
    delete from t_car where id in (1, 2, 3);
    delete from t_car where id=1 or id=2 or id=3;
    
    • 批量添加:
    insert into t_car values
    	(null, '1001', '凯美瑞', 35.0, '2010-10-11', '燃油车')
    	(null, '1002', '比亚迪唐', 31.0, '2020-11-11', '新能源')
    	(null, '1003', '比亚迪宋', 32.0, '2020-10-11', '新能源')
    

批量删除

  • 用in来删除

    CarMapper接口

    /**
     * 根据id批量删除车辆信息,用in
     * 使用标签
     * @param ids
     * @return
     */
    int deleteBatchByForeachWithIn(@Param("ids")Long[] ids);
    

    CarMapper.xml

    <delete id="deleteBatchByForeachWithIn">
        delete from t_car where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        foreach>
    delete>
    

    测试类:

    @Test
    public void testDeleteBatchByForeachWithIn(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        int count = mapper.deleteBatchByForeachWithIn(new Long[]{5L, 6L, 7L});
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第68张图片

  • 用or来删除

    CarMapper接口

    /**
     * 根据id批量删除车辆信息,用or
     * 使用标签
     * @param ids
     * @return
     */
    int deleteBatchByForeachWithOr(@Param("ids")Long[] ids);
    

    CarMapper.xml

    <delete id="deleteBatchByForeachWithOr">
        delete from t_car where
        <foreach collection="ids" item="id" separator="or">
            id = #{id}
        foreach>
    delete>
    

    测试类:

    @Test
    public void testDeleteBatchByForeachWithOr(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        int count = mapper.deleteBatchByForeachWithOr(new Long[]{5L, 6L, 7L});
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第69张图片

批量添加

CarMapper接口

/**
 * 批量添加车辆信息
 * 使用标签
 * @param cars
 * @return
 */
int insertBatchByForeach(@Param("cars")List<Car> cars);

CarMapper.xml

<insert id="insertBatchByForeach">
    insert into t_car values
    <foreach collection="cars" item="car" separator=",">
        (null, #{car.carNum}, #{car.brand}, #{car.guidePrice}, #{car.produceTime}, #{car.carType})
    foreach>
insert>

测试类:

@Test
public void testInsertBatchByForeach(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    Car car1 = new Car(null, "1001", "凯美瑞", 35.0, "2010-10-11", "燃油车");
    Car car2 = new Car(null, "1002", "比亚迪唐", 31.0, "2020-11-11", "新能源");
    Car car3 = new Car(null, "1003", "比亚迪宋", 32.0, "2020-10-11", "新能源");
    List<Car> cars = Arrays.asList(car1, car2, car3);
    int count = mapper.insertBatchByForeach(cars);
    System.out.println(count);
    sqlSession.commit();
    sqlSession.close();
}

执行结果:

在这里插入图片描述

12.7 sql标签与include标签

  • sql标签用来声明sql片段

  • include标签用来将声明的sql片段包含到某个sql语句当中

  • 作用:代码复用。易维护。

    CarMapper接口

    /**
     * 查询所有汽车信息
     * @return
     */
    List<Car> selectAll();
    

    CarMapper.xml

    <sql id="carCols">
        id,
        car_num as carNum,
        brand,
        guide_price as guidePrice,
        produce_time as produceTime,
        car_type as carType
    sql>
    <select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
        select
        <include refid="carCols">include>
        from t_car
    select>
    

    测试类:

    @Test
    public void testSelectAll(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectAll();
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }
    

    执行结果:

    在这里插入图片描述

十三、MyBatis的高级映射及延迟加载

  • 模块名:mybatis-009-advanced-mapping

  • 打包方式:jar

  • 依赖:mybatis依赖、mysql驱动依赖、junit依赖、logback依赖

  • 配置文件:mybatis-config.xml、logback.xml、jdbc.properties

  • 拷贝工具类:SqlSessionUtil

  • 准备数据库表:一个班级对应多个学生。

    • 班级表:t_clazz:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第70张图片

    [记录学习]自学动力节点老杜MyBatis笔记_02_第71张图片

    • 学生表:t_student:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第72张图片

    [记录学习]自学动力节点老杜MyBatis笔记_02_第73张图片

  • 创建pojo:Student、Clazz

    Student.java

    package com.powernode.mybatis.pojo;
    
    /**
     * 学生类
     * @author ShiningSong
     * @version 1.0
     * @since 1.0
     */
    public class Student {
        private Long sid;
        private String sname;
    	 ......
    }
    
    

    Clazz.java

    package com.powernode.mybatis.pojo;
    
    /**
     * 班级类
     * @author ShiningSong
     * @version 1.0
     * @since 1.0
     */
    public class Clazz {
        private Long cid;
        private String cname;
        ......
    }
    
  • 创建mapper接口:StudentMapper、ClazzMapper

  • 创建mapper映射文件:StudentMapper.xml、ClazzMapper.xml

13.1 多对一

[记录学习]自学动力节点老杜MyBatis笔记_02_第74张图片

多种方式,常见的包括三种:

  • 第一种方式:一条SQL语句,级联属性映射。
  • 第二种方式:一条SQL语句,association。
  • 第三种方式:两条SQL语句,分步查询。(这种方式常用:优点一是可复用。优点二是支持懒加 载。)

第一种方式:级联属性映射

  • pojo类Student中添加一个属性:Clazz clazz; 表示学生关联的班级对象。

    package com.powernode.mybatis.pojo;
    
    /**
     * 学生类
     * @author ShiningSong
     * @version 1.0
     * @since 1.0
     */
    public class Student {
        private Long sid;
        private String sname;
        private Clazz clazz;
    
        @Override
        public String toString() {
            return "Student{" +
                    "sid=" + sid +
                    ", sname='" + sname + '\'' +
                    ", clazz=" + clazz +
                    '}';
        }
    
        public Long getSid() {
            return sid;
        }
    
        public void setSid(Long sid) {
            this.sid = sid;
        }
    
        public String getSname() {
            return sname;
        }
    
        public void setSname(String sname) {
            this.sname = sname;
        }
    
        public Clazz getClazz() {
            return clazz;
        }
    
        public void setClazz(Clazz clazz) {
            this.clazz = clazz;
        }
    
        public Student(Long sid, String sname, Clazz clazz) {
            this.sid = sid;
            this.sname = sname;
            this.clazz = clazz;
        }
    
        public Student() {
        }
    
    }
    

    StudentMapper接口

    package com.powernode.mybatis.mapper;
    
    import com.powernode.mybatis.pojo.Student;
    
    /**
     * 学生表映射接口
     * @author ShiningSong
     * @version 1.0
     * @since 1.0
     */
    public interface StudentMapper {
    
        /**
         * 通过sid查询学生信息
         * @param sid
         * @return
         */
        Student selectBySid(Long sid);
    }
    

    StudentMapper.xml

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.powernode.mybatis.mapper.StudentMapper">
    
    
        <resultMap id="studentResultMap" type="Student">
            <id property="sid" column="sid"/>
            <result property="sname" column="sname"/>
            <result property="clazz.cid" column="cid"/>
            <result property="clazz.cname" column="cname"/>
        resultMap>
    
        <select id="selectBySid" resultMap="studentResultMap">
            select s.*, c.*
            from t_student s
            left join t_clazz c
            on s.cid = c.cid
            where sid = #{sid}
        select>
    mapper>
    

    测试类:

    package com.powernode.mybatis.test;
    
    import com.mysql.cj.xdevapi.SqlUpdateResult;
    import com.powernode.mybatis.mapper.StudentMapper;
    import com.powernode.mybatis.pojo.Student;
    import com.powernode.mybatis.utils.SqlSessionUtil;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    public class AdvancedMappingTest {
        @Test
        public void testSelectBySid(){
            SqlSession sqlSession = SqlSessionUtil.openSession();
            StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
            Student student = mapper.selectBySid(2L);
            System.out.println(student);
            sqlSession.close();
        }
    }
    

    执行结果:

    在这里插入图片描述

第二种方式:association

  • 其他位置都不需要修改,只需要修改resultMap中的配置:association即可。

    StudentMapper.xml

    <resultMap id="studentResultMap" type="Student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        
        <association property="clazz" javaType="Clazz">
            <id property="cid" column="cid"/>
            <result property="cname" column="cname"/>
        association>
    resultMap>
    
  • association翻译为:关联。

  • 学生对象关联一个班级对象。

第三种方式:分步查询

  • 其他位置不需要修改,只需要修改以及添加以下三处:

    • 第一处:在ClazzMapper接口中添加方法

    ClazzMapper接口

    package com.powernode.mybatis.mapper;
    
    import com.powernode.mybatis.pojo.Clazz;
    
    /**
     * 班级表映射接口
     * @author ShiningSong
     * @version 1.0
     * @since 1.0
     */
    public interface ClazzMapper {
    
        /**
         * 根据cid查询班级信息
         * @param cid
         * @return
         */
        Clazz selectByCid(Long cid);
    }
    
    • 第二处:在ClazzMapper.xml文件中进行配置

    ClazzMapper.xml

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.powernode.mybatis.mapper.ClazzMapper">
    
        <select id="selectByCid" resultType="Clazz">
            select * from t_clazz where cid = #{cid}
        select>
    mapper>
    
    • 第三处:association中select位置填写sqlId。sqlId=namespace.id。其中column属性作为这条子sql语句的条件。

    StudentMapper.xml

    <resultMap id="studentResultMap" type="Student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        
        
        <association property="clazz"
                     select="com.powernode.mybatis.mapper.ClazzMapper.selectByCid"
                     column="cid"/>
    resultMap>
    
    <select id="selectBySid" resultMap="studentResultMap">
        select s.* from t_student s where sid=#{sid}
    select>
    

    执行结果:可以很明显看到先后有两条sql语句执行

    [记录学习]自学动力节点老杜MyBatis笔记_02_第75张图片

    分步优点:

    • 第一个优点:代码复用性增强。
    • 第二个优点:支持延迟加载。【暂时访问不到的数据可以先不查询。提高程序的执行效率。】

13.2 多对一延迟加载

  • 要想支持延迟加载,非常简单,只需要在association标签中添加fetchType="lazy"即可。

  • 修改StudentMapper.xml文件:

    StudentMapper.xml

    <resultMap id="studentResultMap" type="Student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        <association property="clazz"
                     select="com.powernode.mybatis.mapper.ClazzMapper.selectByCid"
                     column="cid"
                     fetchType="lazy"/>
    resultMap>
    

    测试类:

    @Test
    public void testSelectBySid(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student = mapper.selectBySid(2L);
        //        System.out.println(student);
        // 只获取学生姓名
        System.out.println("学生姓名:" + student.getSname());
        sqlSession.close();
    }
    

    执行结果:

    在这里插入图片描述

    如果后续需要使用到学生所在班级的名称,这个时候才会执行关联的sql语句,修改测试程序:

    @Test
    public void testSelectBySid(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student = mapper.selectBySid(2L);
        //        System.out.println(student);
        // 只获取学生姓名
        System.out.println("学生姓名:" + student.getSname());
        // 到这里想要获取班级名字
        System.out.println("该学生的班级名称:" + student.getClazz().getCname());
        sqlSession.close();
    }
    

    [记录学习]自学动力节点老杜MyBatis笔记_02_第76张图片

    通过以上的执行结果可以看到,只有当使用到班级名称之后,才会执行关联的sql语句,这就是延迟加载。

  • 在mybatis中如何开启全局的延迟加载呢?需要setting配置,如下:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第77张图片

    mybatis-config.xml

    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
    settings>
    

    把fetchType="lazy"去掉。

    测试类:

    @Test
    public void testSelectBySid(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student = mapper.selectBySid(2L);
        //        System.out.println(student);
        // 只获取学生姓名
        System.out.println("学生姓名:" + student.getSname());
        // 到这里想要获取班级名字
        System.out.println("该学生的班级名称:" + student.getClazz().getCname());
        sqlSession.close();
    }
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第78张图片

  • 通过以上的测试可以看出,我们已经开启了全局延迟加载策略。

  • 开启全局延迟加载之后,所有的sql都会支持延迟加载,如果某个sql你不希望它支持延迟加载怎么办呢?

    • 将fetchType设置为eager:

    StudentMapper.xml

    <resultMap id="studentResultMap" type="Student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        <association property="clazz"
                     select="com.powernode.mybatis.mapper.ClazzMapper.selectByCid"
                     column="cid"
                     fetchType="eager"/>
    resultMap>
    

    执行结果:

    在这里插入图片描述

    这样的话,针对某个特定的sql,你就关闭了延迟加载机制。

  • 后期我们要不要开启延迟加载机制,主要看实际的业务需求是怎样的。

13.3 一对多

[记录学习]自学动力节点老杜MyBatis笔记_02_第79张图片

  • 一对多的实现,通常是在一的一方中有List集合属性。

  • 在Clazz类中添加List stus; 属性。

    Clazz.java

    package com.powernode.mybatis.pojo;
    
    import java.util.List;
    
    /**
     * 班级类
     * @author ShiningSong
     * @version 1.0
     * @since 1.0
     */
    public class Clazz {
        private Long cid;
        private String cname;
        private List<Student> students;
    
        @Override
        public String toString() {
            return "Clazz{" +
                    "cid=" + cid +
                    ", cname='" + cname + '\'' +
                    ", students=" + students +
                    '}';
        }
    
        public Long getCid() {
            return cid;
        }
    
        public void setCid(Long cid) {
            this.cid = cid;
        }
    
        public String getCname() {
            return cname;
        }
    
        public void setCname(String cname) {
            this.cname = cname;
        }
    
        public List<Student> getStudents() {
            return students;
        }
    
        public void setStudents(List<Student> students) {
            this.students = students;
        }
    
        public Clazz(Long cid, String cname, List<Student> students) {
            this.cid = cid;
            this.cname = cname;
            this.students = students;
        }
    
        public Clazz() {
        }
    }
    
  • 一对多的实现通常包括两种实现方式:

    • 第一种方式:collection
    • 第二种方式:分步查询

第一种方式:collection

ClazzMapper接口

/**
 * 根据cid查询班级信息
 * 同时也要查询班级的学生信息
 * @param cid
 * @return
 */
Clazz selectClazzAndStudentsByCid(Long cid);

ClazzMapper.xml

<resultMap id="clazzResultMap" type="Clazz">
    <id property="cid" column="cid"/>
    <result property="cname" column="cname"/>
    <collection property="students" ofType="Student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
    collection>
resultMap>

<select id="selectClazzAndStudentsByCid" resultMap="clazzResultMap">
    select *
    from t_clazz c
    left join t_student s
    on c.cid = s.cid
    where c.cid = #{cid}
select>

测试类:

@Test
public void testSelectClazzAndStudentsByCid(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
    Clazz clazz = mapper.selectClazzAndStudentsByCid(1001L);
    System.out.println(clazz);
    sqlSession.close();
}

执行结果:

在这里插入图片描述

Clazz{cid=1001, 
		cname='高三1班', 
		students=[
				Student{sid=1, sname='张三', clazz=null}, 
				Student{sid=2, sname='李四', clazz=null}, 
				Student{sid=3, sname='王五', clazz=null}
				]
}

第二种方式:分步查询

  • 修改以下三个位置即可:

    StudentMapper接口

    /**
     * 根据cid查询学生信息返回List集合
     * @param cid
     * @return
     */
    List<Student> selectByCid(Long cid);
    

    StudentMapper.xml

    <select id="selectByCid" resultType="Student">
        select * from t_student where cid = #{cid}
    select>
    

    ClazzMapper.xml

    <resultMap id="clazzResultMap" type="Clazz">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
        
        <collection property="students"
                    select="com.powernode.mybatis.mapper.StudentMapper.selectByCid"
                    column="cid"/>
    resultMap>
    
    <select id="selectClazzAndStudentsByCid" resultMap="clazzResultMap">
        select * from t_clazz c where c.cid = #{cid}
    select>
    

    测试类:

    @Test
    public void testSelectClazzAndStudentsByCid(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
        Clazz clazz = mapper.selectClazzAndStudentsByCid(1001L);
        System.out.println(clazz);
        sqlSession.close();
    }
    

    执行结果:

    在这里插入图片描述

13.4 一对多延迟加载

  • 一对多延迟加载机制和多对一是一样的。同样是通过两种方式:
    • 第一种:fetchType=“lazy”
    • 第二种:修改全局的配置setting,lazyLoadingEnabled=true,如果开启全局延迟加载,想让某个 sql不使用延迟加载:fetchType=“eager”

十四、MyBatis的缓存

[记录学习]自学动力节点老杜MyBatis笔记_02_第80张图片

  • 缓存:cache
  • 缓存的作用:通过减少IO的方式,来提高程序的执行效率。
  • mybatis的缓存:将select语句的查询结果放到缓存(内存)当中,下一次还是这条select语句的话,直 接从缓存中取,不再查数据库。一方面是减少了IO。另一方面不再执行繁琐的查找算法。效率大大提升。
  • mybatis缓存包括:
    • 一级缓存:将查询到的数据存储到SqlSession中。
    • 二级缓存:将查询到的数据存储到SqlSessionFactory中。
    • 或者集成其它第三方的缓存:比如EhCache【Java语⾔开发的】、Memcache【C语⾔开发的】 等。
  • 缓存只针对于DQL语句,也就是说缓存机制只对应select语句。

14.1 一级缓存

  • 一级缓存默认是开启的。不需要做任何配置。

  • 原理:只要使用同一个SqlSession对象执行同一条SQL语句,就会走缓存。

  • 模块名:mybatis-010-cache

    CarMapper接口

    package com.powernode.mybatis.mapper;
    
    import com.powernode.mybatis.pojo.Car;
    
    /**
     * 汽车表映射接口
     * @author ShiningSong
     * @version 1.0
     * @since 1.0
     */
    public interface CarMapper {
    
        /**
         * 根据id获取Car信息
         * @return
         */
        Car selectById(Long id);
    }
    

    CarMapper.xml

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.powernode.mybatis.mapper.CarMapper">
    
        <select id="selectById" resultType="Car">
            select * from t_car where id=#{id}
        select>
    mapper>
    

    测试类:

    package com.powernode.mybatis.test;
    
    import com.powernode.mybatis.mapper.CarMapper;
    import com.powernode.mybatis.pojo.Car;
    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 org.junit.Test;
    
    import java.io.IOException;
    
    public class CacheTest {
    
        @Test
        public void testSelectById() throws IOException {
            // 不能使用工具类
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
    
            SqlSession sqlSession1 = sqlSessionFactory.openSession();
            CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
            CarMapper mapper2 = sqlSession1.getMapper(CarMapper.class);
            Car car1 = mapper1.selectById(12L);
            System.out.println(car1);
            Car car2 = mapper2.selectById(12L);
            System.out.println(car2);
    
            SqlSession sqlSession2 = sqlSessionFactory.openSession();
            CarMapper mapper3 = sqlSession2.getMapper(CarMapper.class);
            CarMapper mapper4 = sqlSession2.getMapper(CarMapper.class);
            Car car3 = mapper3.selectById(12L);
            System.out.println(car3);
            Car car4 = mapper4.selectById(12L);
            System.out.println(car4);
            
            sqlSession1.close();
            sqlSession2.close();
        }
    }
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第81张图片

  • 什么情况下不走缓存?

    • 第一种:不同的SqlSession对象。
    • 第二种:查询条件变化了。
  • 一级缓存失效情况包括两种:

    • 第一种:第一次查询和第二次查询之间,手动清空了一级缓存。
    Car car1 = mapper1.selectById(12L);
    System.out.println(car1);
    // 手动清空
    sqlSession1.clearCache();
    Car car2 = mapper2.selectById(12L);
    System.out.println(car2);
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第82张图片

    • 第二种:第一次查询和第二次查询之间,执行了增删改操作。【这个增删改和哪张表没有关系,只要有insert delete update操作,一级缓存就失效。】

    CarMapper接口

    /**
     * 插入账户信息
     * 任务:在一级缓存之间做一个CUD操作
     */
    void insertAccount();
    

    CarMapper.xml

    <insert id="insertAccount">
        insert into t_act values (3, 'act003', 10000)
    insert>
    

    测试类:

    Car car1 = mapper1.selectById(12L);
    System.out.println(car1);
    // 插入一次insert操作(对其他表也行)
    mapper1.insertAccount();
    Car car2 = mapper2.selectById(12L);
    System.out.println(car2);
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第83张图片

14.2 二级缓存

  • 二级缓存的范围是SqlSessionFactory。

  • 使用二级缓存需要具备以下几个条件:

  1. 全局性地开启或关闭所有映射器配置文件中已配置 的任何缓存。默认就是true,无需设置。
  2. 在需要使用二级缓存的SqlMapper.xml文件中添加配置:
  3. 使用二级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接口
  4. SqlSession对象关闭或提交之后,一级缓存中的数据才会被写入到二级缓存当中。此时二级缓存才可用。
  • 测试二级缓存:

    Car.java

    package com.powernode.mybatis.pojo;
    
    import java.io.Serializable;
    
    /**
     * 封装汽车的相关信息的pojo类,普通的java类
     * @author ShiningSong
     * @version 1.1
     * @since 1.0
     */
    
    public class Car implements Serializable {
        // 实现Serializable接口
        ......
    }
    

    CarMapper.xml

    <cache/>
    

    测试类:

    @Test
    public void testSelectById2() throws IOException {
        // 不能使用工具类
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
    
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
        Car car1 = mapper1.selectById(12L);
        System.out.println(car1);
    
        // 关键一步
        sqlSession1.close();
    
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        CarMapper mapper3 = sqlSession2.getMapper(CarMapper.class);
        Car car3 = mapper3.selectById(12L);
        System.out.println(car3);
    
        sqlSession2.close();
    }
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第84张图片

    二级缓存的失效:只要两次查询之间出现了增删改操作。二级缓存就会失效。【一级缓存也会失效】

二级缓存的相关配置:

[记录学习]自学动力节点老杜MyBatis笔记_02_第85张图片

  1. eviction:指定从缓存中移除某个对象的淘汰算法。默认采用LRU策略。
    1. LRU:Least Recently Used。最近最少使用。优先淘汰在间隔时间内使用频率最低的对象。(其实还有一种淘汰算法LFU,最不常用。)
    2. FIFO:First In First Out。一种先进先出的数据缓存器。先进入二级缓存的对象最先被淘汰。
    3. SOFT:软引用。淘汰软引用指向的对象。具体算法和JVM的垃圾回收算法有关。
    4. WEAK:弱引用。淘汰弱引用指向的对象。具体算法和JVM的垃圾回收算法有关。
  2. flushInterval:
    1. 二级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存,只要内存足够大,一直会向二级缓存中缓存数据。除非执行了增删改。
  3. readOnly:
    1. true:多条相同的sql语句执行之后返回的对象是共享的同一个。性能好。但是多线程并发可能会存在安全问题。
    2. false:多条相同的sql语句执行之后返回的对象是副本,调用了clone方法。性能一般。但安全。
  4. size:
    1. 设置二级缓存中最多可存储的java对象数量。默认值1024。

14.3 MyBatis集成EhCache

  • 集成EhCache是为了代替mybatis自带的二级缓存。一级缓存是无法替代的。

  • mybatis对外提供了接口,也可以集成第三方的缓存组件。比如EhCache、Memcache等。都可以。

  • EhCache是Java写的。Memcache是C语⾔写的。所以mybatis集成EhCache较为常见,按照以下步骤操 作,就可以完成集成:

    • 第一步:pom.xml引入mybatis整合ehcache的依赖。
    <dependency>
        <groupId>org.mybatis.cachesgroupId>
        <artifactId>mybatis-ehcacheartifactId>
        <version>1.2.2version>
    dependency>
    
    <dependency>
        <groupId>ch.qos.logbackgroupId>
        <artifactId>logback-classicartifactId>
        <version>1.2.11version>
    dependency>
    
    • 第二步:在类的根路径下新建echcache.xml文件,并提供以下配置信息。
    
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
             updateCheck="false">
        
        <diskStore path="e:/ehcache"/>
    
        
        
        
        
        
        
        
        
        
        
        
        <defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>
    ehcache>
    
    • 第三步:修改SqlMapper.xml文件中的标签,添加type属性。

    CarMapper.xml

    <cache type="org.mybatis.caches.ehcache.EhcacheCache" />
    

    测试类:

    @Test
    public void testSelectById2() throws IOException {
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
    
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
        Car car1 = mapper1.selectById(12L);
        System.out.println(car1);
    
        sqlSession1.close();
    
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        CarMapper mapper3 = sqlSession2.getMapper(CarMapper.class);
        Car car3 = mapper3.selectById(12L);
        System.out.println(car3);
    
        sqlSession2.close();
    }
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第86张图片

十五、MyBatis的逆向工程

  • 所谓的逆向工程是:根据数据库表逆向生成Java的pojo类,SqlMapper.xml文件,以及Mapper接口类等。
  • 要完成这个工作,需要借助别⼈写好的逆向工程插件。
  • 思考:使用这个插件的话,需要给这个插件配置哪些信息?
    • pojo类名、包名以及生成位置。
    • SqlMapper.xml文件名以及生成位置。
    • Mapper接口名以及生成位置。
    • 连接数据库的信息。
    • 指定哪些表参与逆向工程。

15.1 逆向工程配置与生成

第一步:基础环境准备

  • 新建模块:mybatis-011-generator
  • 打包方式:jar

第二步:在pom中添加逆向工程插件


<build>
     
     <plugins>
         
         <plugin>
             
             <groupId>org.mybatis.generatorgroupId>
             <artifactId>mybatis-generator-maven-pluginartifactId>
             <version>1.4.1version>
             
             <configuration>
             	<overwrite>trueoverwrite>
             configuration>
             
             <dependencies>
                 
                 <dependency>
                 <groupId>mysqlgroupId>
                 <artifactId>mysql-connector-javaartifactId>
                 <version>8.0.30version>
                 dependency>
             dependencies>
         plugin>
     plugins>
build>

第三步:配置generatorConfig.xml

  • 该文件名必须叫做:generatorConfig.xml

  • 该文件必须放在类的根路径下。

    
    DOCTYPE generatorConfiguration
            PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
            "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    <generatorConfiguration>
        
        <context id="DB2Tables" targetRuntime="MyBatis3">
            
            <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>
    
            <commentGenerator>
                
                <property name="suppressDate" value="true"/>
                
                <property name="suppressAllComments" value="true"/>
            commentGenerator>
            
            <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                            connectionURL="jdbc:mysql://localhost:3306/powernode"
                            userId="root"
                            password="root">
            jdbcConnection>
            
            <javaModelGenerator targetPackage="com.powernode.mybatis.pojo" targetProject="src/main/java">
                
                <property name="enableSubPackages" value="true"/>
                
                <property name="trimStrings" value="true"/>
            javaModelGenerator>
            
            <sqlMapGenerator targetPackage="com.powernode.mybatis.mapper" targetProject="src/main/resources">
                
                <property name="enableSubPackages" value="true"/>
            sqlMapGenerator>
            
            <javaClientGenerator
                    type="xmlMapper"
                    targetPackage="com.powernode.mybatis.mapper"
                    targetProject="src/main/java">
                <property name="enableSubPackages" value="true"/>
            javaClientGenerator>
            
            <table tableName="t_car" domainObjectName="Car"/>
        context>
    generatorConfiguration>
    

第四步:运行插件

[记录学习]自学动力节点老杜MyBatis笔记_02_第87张图片

15.2 测试逆向工程生成的是否好用

第一步:环境准备

  • 依赖:mybatis依赖、mysql驱动依赖、junit依赖、logback依赖
  • jdbc.properties
  • mybatis-config.xml
  • logback.xml

第二步:编写测试程序

package com.powernode.mybatis.test;

import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.pojo.CarExample;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.math.BigDecimal;
import java.util.List;

public class GeneratorCompleteTest {

    // CarExample类负责封装查询条件的。
    @Test
    public void testSelect(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        // 执行查询
        // 1. 查询一个
        Car car = mapper.selectByPrimaryKey(12L);
        System.out.println(car);
        // 2. 查询所有(selectByExample,根据条件查询,如果条件是null表示没有条件。)
        List<Car> cars = mapper.selectByExample(null);
        cars.forEach(car1 -> System.out.println(car1));
        System.out.println("=========================================");
        // 3. 按照条件进行查询
        // QBC 风格:Query By Criteria 一种查询方式,比较面向对象,看不到sql语句。
        // 封装条件,通过CarExample对象来封装查询条件
        CarExample carExample = new CarExample();
        // 调用carExample.createCriteria()方法来创建查询条件
        carExample.createCriteria()
                .andBrandLike("帕萨特")
                .andGuidePriceGreaterThan(new BigDecimal(20.0));
        // 添加or
        carExample.or().andCarTypeEqualTo("燃油车");
        // 执行查询
        List<Car> cars2 = mapper.selectByExample(carExample);
        cars2.forEach(car2 -> System.out.println(car2));

        sqlSession.close();
    }
}

十六、MyBatis使用PageHelper

[记录学习]自学动力节点老杜MyBatis笔记_02_第88张图片

16.1 limit分页

  • mysql的limit后面两个数字:
    • 第一个数字:startIndex(起始下标。下标从0开始。)
    • 第二个数字:pageSize(每页显示的记录条数)
  • 假设已知页码pageNum,还有每页显示的记录条数pageSize,第一个数字可以动态的获取吗?
    • startIndex = (pageNum - 1) * pageSize
  • 所以,标准通用的mysql分页SQL:
select 
	*
from 
	tableName .......
limit
	(pageNum - 1) * pageSize, pageSize
  • 使用mybatis应该怎么做?
  • 模块名:mybatis-012-page

CarMapper接口

package com.powernode.mybatis.mapper;

import com.powernode.mybatis.pojo.Car;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 汽车表映射接口
 */
public interface CarMapper {

    /**
     * 通过分页的方式获取汽车列表信息
     * @param startIndex 页码
     * @param pageSize 每页显示记录条数
     * @return
     */
    List<Car> selectAllPage(@Param("startIndex")Integer startIndex, @Param("pageSize")Integer pageSize);
}

CarMapper.xml


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">

    <select id="selectAllPage" resultType="Car">
        select * from t_car
        limit #{startIndex}, #{pageSize}
    select>
mapper>

测试类:

package com.powernode.mybatis.test;

import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class PageTest {

    @Test
    public void testSelectAllPage(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        // 页码
        Integer pageNum = 2;
        // 每页显示记录条数
        Integer pageSize = 3;
        // 起始下标
        Integer startIndex = (pageNum - 1) * pageSize;
        List<Car> cars = mapper.selectAllPage(startIndex, pageSize);
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }
}

执行结果:

[记录学习]自学动力节点老杜MyBatis笔记_02_第89张图片

[记录学习]自学动力节点老杜MyBatis笔记_02_第90张图片

  • 获取数据不难,难的是获取分页相关的数据比较难。可以借助mybatis的PageHelper插件。

16.3 PageHelper插件

  • 使用PageHelper插件进行分页,更加的便捷。

第一步:引入依赖


<dependency>
    <groupId>com.github.pagehelpergroupId>
    <artifactId>pagehelperartifactId>
    <version>5.3.1version>
dependency>

第二步:在mybatis-config.xml文件中配置插件

  • typeAliases标签下面进行配置:
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">plugin>
plugins>

第三步:编写Java代码

CarMapper接口

/**
 * 查询所有车辆信息
 * @return
 */
List<Car> selectAll();

CarMapper.xml

<select id="selectAll" resultType="Car">
    select * from t_car
select>
  • 关键点:
    • 在查询语句之前开启分页功能。
    • 在查询语句之后封装PageInfo对象。(PageInfo对象将来会存储到request域当中。在页面上展示。)

测试类:

@Test
public void testSelectAll(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    // 开启分页
    PageHelper.startPage(2,3);
    // 执行查询语句
    List<Car> cars = mapper.selectAll();
    // 获取分页信息对象
    PageInfo<Car> pageInfo = new PageInfo<>(cars, 5);
    System.out.println(pageInfo);
    sqlSession.close();
}

执行结果:

PageInfo{
	pageNum=2, pageSize=3, size=3, startRow=4, endRow=6, total=24, pages=8,
	list=Page{count=true, pageNum=2, pageSize=3, startRow=3, endRow=6, total=24, pages=8, reasonable=false, pageSizeZero=false}
	[
		Car{id=8, carNum='1003', brand='丰田霸道', guidePrice=30.0, produceTime='2000-10-11', carType='燃油车'}, 
		Car{id=9, carNum='102', brand='比亚迪汉', guidePrice=30.23, produceTime='2018-09-10', carType='电车'}, 
		Car{id=10, carNum='1003', brand='丰田霸道', guidePrice=30.0, produceTime='2000-10-11', carType='燃油车'}
	], 
	prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, 
	navigatePages=5, navigateFirstPage=1, navigateLastPage=5, navigatepageNums=[1, 2, 3, 4, 5]
}

十七、MyBatis的注解式开发

  • mybatis中也提供了注解式开发方式,采用注解可以减少Sql映射文件的配置。
  • 当然,使用注解式开发的话,sql语句是写在java程序中的,这种方式也会给sql语句的维护带来成本。 官方是这么说的:

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

  • 使用注解编写复杂的SQL是这样的:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第91张图片

  • 原则:简单sql可以注解。复杂sql使用xml。

  • 模块名:mybatis-013-annotation

  • 打包方式:jar

  • 依赖:mybatis,mysql驱动,junit,logback

  • 配置文件:jdbc.properties、mybatis-config.xml、logback.xml

  • pojo:com.powernode.mybatis.pojo.Car

  • mapper接口:com.powernode.mybatis.mapper.CarMapper

17.1 @Insert

CarMapper接口

/**
 * 使用@Insert()注解插入车辆信息
 * @return
 */
@Insert(value = "insert into t_car values(null, #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType})")
int insert(Car car);

测试类:

@Test
public void testInsert(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    Car car = new Car(null, "1112", "卡罗拉", 30.0, "2000-10-10", "燃油车");
    int count = mapper.insert(car);
    System.out.println("插入了几条数据:" + count);
    sqlSession.commit();
    sqlSession.close();
}

17.2 @Delete

CarMapper接口

/**
 * 使用@Delete()注解删除车辆信息
 * @param id
 * @return
 */
@Delete("delete from t_car where id = #{id}")
int deleteById(Long id);

测试类:

@Test
public void testDeleteById(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    int count = mapper.deleteById(29L);
    System.out.println("删除了几条记录:" + count);
    sqlSession.commit();
    sqlSession.close();
}

17.3 @Update

CarMapper接口

/**
 * 使用@Update()注解更新车辆信息
 * @param car
 * @return
 */
@Update("update t_car set car_num=#{carNum}, brand=#{brand}, guide_price=#{guidePrice}, produce_time=#{produceTime}, car_type=#{carType} where id = #{id}")
int updateById(Car car);

测试类:

@Test
public void testUpdateById(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    Car car = new Car(44L, "1001", "凯美瑞", 30.5, "2000-11-10", "新能源");
    int count = mapper.updateById(car);
    System.out.println("更新了几条数据:" + count);
    sqlSession.commit();
    sqlSession.close();
}

17.4 @Select

CarMapper接口

/**
 * 使用@Select()、@Results({@Result()...})注解更新车辆信息
 * @param id
 * @return
 */
@Select("select * from t_car where id = #{id}")
@Results({
        @Result(column = "id", property = "id", id = true),
        @Result(column = "car_num", property = "carNum"),
        @Result(column = "brand", property = "brand"),
        @Result(column = "guide_price", property = "guidePrice"),
        @Result(column = "produce_time", property = "produceTime"),
        @Result(column = "car_type", property = "carType"),
})
Car selectById(Long id);

测试类:

    @Test
    public void testSelectById(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = mapper.selectById(44L);
        System.out.println(car);
        sqlSession.close();
    }

执行结果:

在这里插入图片描述

五、手写MyBatis框架(掌握原理)

  • 警示:该部分内容有难度,基础较弱的程序员可能有些部分是听不懂的,如果无法跟下来,可直接跳 过,不影响后续知识点的学习。
  • 当然,如果你要能够跟下来,必然会让你加深对MyBatis框架的理解。 我们给自己的框架起个名:GodBatis(起名灵感来源于:my god!!! 我的天呢!)

5.1 dom4j解析XML文件

  • 该部分内容不再赘述,不会解析XML的,请观看⽼杜前面讲解的dom4j解析XML文件的视频。

  • 模块名:parse-xml-by-dom4j(普通的Java Maven模块)

  • 第一步:引入dom4j的依赖

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
    
        <groupId>org.groupgroupId>
        <artifactId>parse-xml-by-dom4jartifactId>
        <version>1.0-SNAPSHOTversion>
        <packaging>jarpackaging>
    
        <dependencies>
            
            <dependency>
                <groupId>org.dom4jgroupId>
                <artifactId>dom4jartifactId>
                <version>2.1.3version>
            dependency>
            
            <dependency>
                <groupId>jaxengroupId>
                <artifactId>jaxenartifactId>
                <version>1.2.0version>
            dependency>
            
            <dependency>
                <groupId>junitgroupId>
                <artifactId>junitartifactId>
                <version>4.13.2version>
                <scope>testscope>
            dependency>
        dependencies>
    
        <properties>
            <maven.compiler.source>8maven.compiler.source>
            <maven.compiler.target>8maven.compiler.target>
            <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        properties>
    
    project>
    
  • 第二步:编写配置文件godbatis-config.xml

    
    DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    
    <configuration>
        <environments default="powernodeDB">
            <environment id="powernodeDB">
                <transactionManager type="MANAGED"/>
                <dataSource type="UNPOOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                dataSource>
            environment>
            <environment id="mybatisDB">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                dataSource>
            environment>
        environments>
        <mappers>
            <mapper resource="CarMapper.xml"/>
        mappers>
    configuration>
    
  • 第三步:解析godbatis-config.xml

    @Test
    public void testParseMyBatisConfigXML() throws DocumentException {
        // 创建SAXReader对象
        SAXReader reader = new SAXReader();
        // 获取输入流
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
        // 读取XML文件
        Document document = reader.read(is);
        //        // 获取文档当中的根标签
        //        Element rootElt = document.getRootElement();
        //        String rootEltName = rootElt.getName();
        //        System.out.println("根节点的名字:" + rootEltName);
        // 获取default默认的环境id
        // xpath是做标签路径匹配的,能够让我们快速定位XML文件中的元素。
        // 以下的xpath代表了:从根下开始找configuration标签,然后找configuration标签下的子标签environments
        String xpath = "/configuration/environments";
        Element environments = (Element) document.selectSingleNode(xpath); // Element是Node类的子类,方法更多,使用更便捷。
        // 获取属性的值
        String defaultEnvironmentId = environments.attributeValue("default");
        System.out.println("默认环境的id:" + defaultEnvironmentId);
        // 具体的环境environment
        xpath = "/configuration/environments/environment[@id='" + defaultEnvironmentId + "']";
        //        System.out.println(xpath);
        Element environment = (Element) document.selectSingleNode(xpath);
        // 获取environment节点下的transactionManager节点(Element的element()方法用来获取孩子节点)
        Element transactionManager = environment.element("transactionManager");
        String transactionType = transactionManager.attributeValue("type");
        System.out.println("事务管理器的类型:" + transactionType);
        // 获取dataSource节点
        Element dataSource = environment.element("dataSource");
        String dataSourceType = dataSource.attributeValue("type");
        System.out.println("数据源的类型:" + dataSourceType);
        // 获取dataSource节点下的所有子节点
        List<Element> propertyElts = dataSource.elements();
        // 遍历
        propertyElts.forEach(propertyElt -> {
            String name = propertyElt.attributeValue("name");
            String value = propertyElt.attributeValue("value");
            System.out.println(name + "=" + value);
        });
        // 获取所有的mapper标签
        // 不想从根下开始获取,你想从任意位置开始,获取所有的某个标签,xpath该这样写
        xpath = "//mapper";
        List<Node> mappers = document.selectNodes(xpath);
        // 遍历
        mappers.forEach(mapper -> {
            Element mapperElt = (Element) mapper;
            String resource = mapperElt.attributeValue("resource");
            System.out.println(resource);
        });
    
    }
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第92张图片

  • 第四步:编写配置文件sqlmapper.xml

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="car">
        <insert id="insertCar">
            insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
        insert>
        <select id="selectById" resultType="com.powernode.mybatis.pojo.Car">
            select
                id,car_num as carNum,brand,guide_price as guidePrice,
                produce_time as produceTime,
                car_type as carType
            from
                t_car
            where
                id = #{id}
        select>
    mapper>
    
  • 第五步:解析sqlmapper.xml

    @Test
    public void testParseSqlMapperXML() throws DocumentException {
        SAXReader reader = new SAXReader();
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("CarMapper.xml");
        Document document = reader.read(is);
        // 获取namespace
        String xpath = "/mapper";
        Element mapper = (Element) document.selectSingleNode(xpath);
        String namespace = mapper.attributeValue("namespace");
        System.out.println(namespace);
        // 获取mapper节点下的所有子节点
        List<Element> elements = mapper.elements();
        // 遍历
        elements.forEach(element -> {
            // 获取sqlId
            String id = element.attributeValue("id");
            System.out.print(id+"\t");
            // 获取resultType
            String resultType = element.attributeValue("resultType");
            System.out.println(resultType);
            // 获取标签中的sql语句(表示获取标签中的文本内容,而且去除前后空白)
            String sql = element.getTextTrim();
            System.out.println(sql);
            // insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
            // insert into t_car values(null,?,?,?,?,?)
            // mybatis封装了jdbc。早晚要执行带有?的sql语句
            // 转换
            String newSql = sql.replaceAll("#\\{[0-9A-Za-z_$]*}", "?");
            System.out.println(newSql);
        });
    }
    

    执行结果:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第93张图片

5.2 GodBatis

  • 手写框架之前,如果没有思路,可以先参考一下mybatis的客户端程序,通过客户端程序来逆推需要的 类,参考代码:

    @Test
    public void testInsert(){
        SqlSession sqlSession = null;
        try {
            // 1.创建SqlSessionFactoryBuilder对象
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSession
                FactoryBuilder();
            // 2.创建SqlSessionFactory对象
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.bui
                ld(Resources.getResourceAsStream("mybatis-config.xml"));
            // 3.创建SqlSession对象
            sqlSession = sqlSessionFactory.openSession();
            // 4.执行SQL
            Car car = new Car(null, "111", "宝⻢X7", "70.3", "2010-10-11", "燃油车");
            int count = sqlSession.insert("insertCar",car);
            System.out.println("更新了几条记录:" + count);
            // 5.提交
            sqlSession.commit();
        } catch (Exception e) {
            // 回滚
            if (sqlSession != null) {
                sqlSession.rollback();
            }
            e.printStackTrace();
        } finally {
            // 6.关闭
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }
    @Test
    public void testSelectOne(){
        SqlSession sqlSession = null;
        try {
            // 1.创建SqlSessionFactoryBuilder对象
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            // 2.创建SqlSessionFactory对象
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
            // 3.创建SqlSession对象
            sqlSession = sqlSessionFactory.openSession();
            // 4.执行SQL
            Car car = (Car)sqlSession.selectOne("selectCarByCarNum", "111");
            System.out.println(car);
            // 5.提交
            sqlSession.commit();
        } catch (Exception e) {
            // 回滚
            if (sqlSession != null) {
                sqlSession.rollback();
            }
            e.printStackTrace();
        } finally {
            // 6.关闭
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }
    

第一步:IDEA中创建模块

  • 模块:godbatis(创建普通的Java Maven模块,打包方式jar),引入相关依赖

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
    
        <groupId>org.godgroupId>
        <artifactId>godbatisartifactId>
        <version>1.0-SNAPSHOTversion>
        <packaging>jarpackaging>
    
        <dependencies>
            <dependency>
                <groupId>org.dom4jgroupId>
                <artifactId>dom4jartifactId>
                <version>2.1.3version>
            dependency>
            <dependency>
                <groupId>jaxengroupId>
                <artifactId>jaxenartifactId>
                <version>1.2.0version>
            dependency>
            <dependency>
                <groupId>junitgroupId>
                <artifactId>junitartifactId>
                <version>4.13.2version>
                <scope>testscope>
            dependency>
        dependencies>
    
        <properties>
            <maven.compiler.source>8maven.compiler.source>
            <maven.compiler.target>8maven.compiler.target>
            <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        properties>
    
    project>
    

第二步:资源工具类,方便获取指向配置文件的输入流

Resources.java

package org.god.core;


import java.io.InputStream;

/**
 * 资源工具类
 * @author ShiningSong
 * @version 1.0
 * @since 1.0
 */
public class Resources {

    /**
     * 从类路径中获取配置文件的输入流
     * @param config
     * @return 输入流,该输入流执行类路径中的配置文件
     */
    public static InputStream getResourceAsStream(String config){
        return Thread.currentThread().getContextClassLoader().getResourceAsStream(config);
    }
}

第三步:定义SqlSessionFactoryBuilder类

  • 提供一个无参数构造方法,再提供一个build方法,该build方法要返回SqlSessionFactory对象

    package org.god.core;
    
    import org.dom4j.Document;
    import org.dom4j.DocumentException;
    import org.dom4j.Element;
    import org.dom4j.io.SAXReader;
    
    import javax.sql.DataSource;
    import java.io.InputStream;
    import java.util.HashMap;
    import java.util.Map;
    
    
    /**
     * SqlSessionFactory对象构建器
     *
     * @author ShiningSong
     * @version 1.0
     * @since 1.0
     */
    public class SqlSessionFactoryBuilder {
    
        /**
         * 创建构建器对象
         */
        public SqlSessionFactoryBuilder() {
        }
    
        /**
         * 获取SqlSessionFactory对象
         * 该方法的主要功能是:读取godbatis核心配置文件,并构建SqlSessionFactory对象
         * @param inputStream 指向核心配置文件的输入流
         * @return SqlSessionFactory对象
         */
        public SqlSessionFactory build(InputStream inputStream) throws DocumentException {
            // 解析配置文件,创建数据源对象
            // 解析配置文件,创建事务管理器对象
            // 解析配置文件,获取所有的SQL映射对象
            // 将以上信息封装到SqlSessionFactory对象中
            // 返回
            return null;
        }
    }
    

第四步:分析SqlSessionFactory类中有哪些属性

  • 事务管理器
    • GodJDBCTransaction
  • SQL映射对象集合
    • Map>

第五步:定义GodJDBCTransaction

  • 事务管理器最好是定义一个接口,然后每一个具体的事务管理器都实现这个接口。

    TransactionManager接口

    package org.god.core;
    
    import java.sql.Connection;
    
    /**
     * 事务管理器接口
     * @author ShiningSong
     * @version 1.0
     * @since 1.0
     */
    public interface TransactionManager {
    
        /**
         * 提交事务
         */
        void commit();
    
        /**
         * 回滚事务
         */
        void rollback();
    
        /**
         * 关闭事务
         */
        void close();
    
        /**
         * 开启连接
         */
        void openConnection();
    
        /**
         * 获取连接对象
         * @return
         */
        Connection getConnection();
    }
    

    GodJDBCTransaction.java

    package org.god.core;
    
    import javax.sql.DataSource;
    import java.sql.Connection;
    import java.sql.SQLException;
    
    /**
     * JDBC事务管理器
     * @author ShiningSong
     * @version 1.0
     * @since 1.0
     */
    public class GodJDBCTransaction implements TransactionManager{
    
        /**
         * 连接对象,控制事务时需要
         */
        private Connection conn;
        /**
         * 数据源对象
         */
        private DataSource dataSource;
        /**
         * 自动提交标志
         * true表示自动提交
         * false表示手动提交
         */
        private boolean autoCommit;
    
        /**
         * 构造事务管理器对象
         * @param dataSource
         * @param autoCommit
         */
        public GodJDBCTransaction(DataSource dataSource, boolean autoCommit) {
            this.dataSource = dataSource;
            this.autoCommit = autoCommit;
        }
    
        @Override
        public void commit() {
            try {
                conn.commit();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void rollback() {
            try {
                conn.rollback();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void close() {
            try {
                conn.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void openConnection() {
            try {
                this.conn = dataSource.getConnection();
                this.conn.setAutoCommit(this.autoCommit);
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public Connection getConnection() {
            return conn;
        }
    }
    

第六步:事务管理器中需要数据源,定义GodUNPOOLEDDataSource

package org.god.core;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

/**
 * 数据源实现类,不使用连接池
 * @author ShiningSong
 * @version 1.0
 * @since 1.0
 */
public class GodUNPOOLEDDataSource implements javax.sql.DataSource{

    private String url;
    private String username;
    private String password;

    public GodUNPOOLEDDataSource(String driver, String url, String username, String password) {
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        this.url = url;
        this.username = username;
        this.password = password;
    }

    @Override
    public Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, username, password);
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

第七步:定义GodMappedStatement

package org.god.core;

/**
 * SQL映射实体类
 * @author ShiningSong
 * @version 1.0
 * @since 1.0
 */
public class GodMappedStatement {
    private String sqlId;
    private String resultType;
    private String sql;
    private String parameterType;
    private String sqlType;

    @Override
    public String toString() {
        return "GodMappedStatement{" +
                "sqlId='" + sqlId + '\'' +
                ", resultType='" + resultType + '\'' +
                ", sql='" + sql + '\'' +
                ", parameterType='" + parameterType + '\'' +
                ", sqlType='" + sqlType + '\'' +
                '}';
    }

    public GodMappedStatement() {
    }

    public GodMappedStatement(String sqlId, String resultType, String sql, String parameterType, String sqlType) {
        this.sqlId = sqlId;
        this.resultType = resultType;
        this.sql = sql;
        this.parameterType = parameterType;
        this.sqlType = sqlType;
    }


    public String getSqlId() {
        return sqlId;
    }

    public void setSqlId(String sqlId) {
        this.sqlId = sqlId;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }

    public String getParameterType() {
        return parameterType;
    }

    public void setParameterType(String parameterType) {
        this.parameterType = parameterType;
    }

    public String getSqlType() {
        return sqlType;
    }

    public void setSqlType(String sqlType) {
        this.sqlType = sqlType;
    }
}

第八步:完善SqlSessionFactory类

package org.god.core;

import java.util.Map;

/**
 * SqlSession工厂对象,使用SqlSessionFactory可以获取会话对象
 * @author ShiningSong
 * @version 1.0
 * @since 1.0
 */
public class SqlSessionFactory {
    private TransactionManager transactionManager;
    private Map<String, GodMappedStatement> mappedStatemens;


    public SqlSession openSession(){
        transactionManager.openConnection();
        SqlSession sqlSession = new SqlSession(transactionManager, mappedStatemens);
        return sqlSession;
    }

    public SqlSessionFactory() {
    }

    public SqlSessionFactory(TransactionManager transactionManager, Map<String, GodMappedStatement> mappedStatemens) {
        this.transactionManager = transactionManager;
        this.mappedStatemens = mappedStatemens;
    }

    public TransactionManager getTransactionManager() {
        return transactionManager;
    }

    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public Map<String, GodMappedStatement> getMappedStatemens() {
        return mappedStatemens;
    }

    public void setMappedStatemens(Map<String, GodMappedStatement> mappedStatemens) {
        this.mappedStatemens = mappedStatemens;
    }
}

第九步:完善SqlSessionFactoryBuilder中的build方法

package org.god.core;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.sql.DataSource;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;


/**
 * SqlSessionFactory对象构建器
 *
 * @author ShiningSong
 * @version 1.0
 * @since 1.0
 */
public class SqlSessionFactoryBuilder {

    /**
     * 创建构建器对象
     */
    public SqlSessionFactoryBuilder() {
    }

    /**
     * 获取SqlSessionFactory对象
     * 该方法的主要功能是:读取godbatis核心配置文件,并构建SqlSessionFactory对象
     * @param inputStream 指向核心配置文件的输入流
     * @return SqlSessionFactory对象
     */
    public SqlSessionFactory build(InputStream inputStream) throws DocumentException {
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(inputStream);
        Element environmentsElt = (Element) document.selectSingleNode("/configuration/environments");
        String defaultEnv = environmentsElt.attributeValue("default");
        Element environmentElt = (Element) document.selectSingleNode("/configuration/environments/environment[@id='" + defaultEnv + "']");
        // 解析配置文件,创建数据源对象
        Element dataSourceElt = environmentElt.element("dataSource");
        DataSource dataSource = getDataSource(dataSourceElt);
        // 解析配置文件,创建事务管理器对象
        Element transactionManagerElt = environmentElt.element("transactionManager");
        TransactionManager transactionManager = getTransactionManager(transactionManagerElt, dataSource);
        // 解析配置文件,获取所有的SQL映射对象
//        Element mappersElt = environmentsElt.element("mappers");
        Element mappersElt = (Element) document.selectSingleNode("/configuration/mappers");
        Map<String, GodMappedStatement> mappedStatements = getMappedStatements(mappersElt);
        // 将以上信息封装到SqlSessionFactory对象中
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactory(transactionManager, mappedStatements);
        // 返回
        return sqlSessionFactory;
    }

    /**
     * 通过mapperElt获取SQL映射实体类
     * @param mappersElt
     * @return
     */
    private Map<String, GodMappedStatement> getMappedStatements(Element mappersElt){
        Map<String, GodMappedStatement> mappedStatements = new HashMap<>();
        mappersElt.elements().forEach(mapperElt -> {
            try {
                String resource = mapperElt.attributeValue("resource");
                SAXReader saxReader = new SAXReader();
                Document document = saxReader.read(Resources.getResourceAsStream(resource));
                Element mapper = (Element) document.selectSingleNode("/mapper");
                String namespace = mapper.attributeValue("namespace");
                mapper.elements().forEach(sqlMapper -> {
                    String sqlId = sqlMapper.attributeValue("id");
                    String resultType = sqlMapper.attributeValue("resultType");
                    String sql = sqlMapper.getTextTrim();
                    String parameterType = sqlMapper.attributeValue("parameterType");
                    String sqlType = sqlMapper.getName().toLowerCase();
                    GodMappedStatement godMappedStatement = new GodMappedStatement(sqlId, resultType, sql, parameterType, sqlType);
                    mappedStatements.put(namespace + "." + sqlId, godMappedStatement);
                });
            } catch (DocumentException e) {
                throw new RuntimeException(e);
            }

        });

        return mappedStatements;
    }

    /**
     * 通过transactionManagerElt和数据源信息创建事务
     * @param transactionManagerElt
     * @param dataSource
     * @return
     */
    private TransactionManager getTransactionManager(Element transactionManagerElt, DataSource dataSource) {
        String type = transactionManagerElt.attributeValue("type").toUpperCase();
        TransactionManager transactionManager = null;
        if("JDBC".equals(type)){
            // 使用JDBC事务
            transactionManager = new GodJDBCTransaction(dataSource, false);
        }else if ("MANAGED".equals(type)){
            // 使用MANAGED事务,事务管理器是交给JEE容器的
        }
        return transactionManager;
    }

    /**
     * 通过dataSourceElt获取数据源信息
     * @param dataSourceElt
     * @return
     */
    private DataSource getDataSource(Element dataSourceElt) {
        // 获取所有的数据源的属性配置
        Map<String, String> dataSourceMap = new HashMap<>();
        dataSourceElt.elements().forEach(propertyElt -> {
            dataSourceMap.put(propertyElt.attributeValue("name"), propertyElt.attributeValue("value"));
        });
        String dataSourceType = dataSourceElt.attributeValue("type").toUpperCase();
        DataSource dataSource = null;
        if ("UNPOOLED".equals(dataSourceType)) {
            dataSource = new GodUNPOOLEDDataSource(dataSourceMap.get("driver"), dataSourceMap.get("url"), dataSourceMap.get("username"),dataSourceMap.get("password"));
        } else if ("POOLED".equals(dataSourceType)) {

        } else if ("JNDI".equals(dataSourceType)){

        }
        return dataSource;
    }
}
  • MappedStatement类的封装:

[记录学习]自学动力节点老杜MyBatis笔记_02_第94张图片

第十步:在SqlSessionFactory中添加openSession方法

public SqlSession openSession(){
    transactionManager.openConnection();
    SqlSession sqlSession = new SqlSession(transactionManager, mappedStatemens);
    return sqlSession;
}

第十一步:编写SqlSession类中commit rollback close方法

package org.god.core;

import jdk.nashorn.internal.ir.CallNode;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;

/**
 * 创建数据库会话对象
 *
 * @author ShiningSong
 * @version 1.0
 * @since 1.0
 */
public class SqlSession {
    private TransactionManager transactionManager;
    private Map<String, GodMappedStatement> mappedStatements;

    public SqlSession(TransactionManager transactionManager, Map<String, GodMappedStatement> mappedStatements) {
        this.transactionManager = transactionManager;
        this.mappedStatements = mappedStatements;
    }

    public void commit() {
        try {
            transactionManager.getConnection().commit();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void rollback() {
        try {
            transactionManager.getConnection().rollback();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void close() {
        try {
            transactionManager.getConnection().close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

第十二步:编写SqlSession类中的insert方法

/**
 * 插入数据
 * @param sqlId 要执行的sqlId
 * @param obj   要插入的数据
 * @return
 */
public int insert(String sqlId, Object obj) {
    GodMappedStatement godMappedStatement = mappedStatements.get(sqlId);
    Connection connection = transactionManager.getConnection();
    // 获取sql语句
    String godbatisSql = godMappedStatement.getSql();
    String sql = godbatisSql.replaceAll("#\\{[a-z0-9A-Z_\\$]*}", "?");

    //重点一步
    Map<Integer, String> map = new HashMap<>();
    int index = 1;
    while (godbatisSql.indexOf("#") >= 0) {
        int beginIndex = godbatisSql.indexOf("#") + 2;
        int endIndex = godbatisSql.indexOf("}");
        map.put(index++, godbatisSql.substring(beginIndex, endIndex).trim());
        godbatisSql = godbatisSql.substring(endIndex + 1);
    }
    final PreparedStatement ps;
    try {
        ps = connection.prepareStatement(sql);

        // 给?赋值
        map.forEach((k, v) -> {
            try {
                String getMethodName = "get" + v.toUpperCase().charAt(0) + v.substring(1);
                Method getMethod = obj.getClass().getDeclaredMethod(getMethodName);
                ps.setString(k, getMethod.invoke(obj).toString());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        int count = ps.executeUpdate();
        ps.close();
        return count;
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
}

第十三步:编写SqlSession类中的selectOne方法

/**
 * 查询一个对象
 * @param sqlId
 * @param parameterObj
 * @return
 */
public Object selectOne(String sqlId, Object parameterObj) {
    GodMappedStatement godMappedStatement = mappedStatements.get(sqlId);
    Connection connection = transactionManager.getConnection();
    // 获取sql语句
    String godbatisSql = godMappedStatement.getSql();
    String sql = godbatisSql.replaceAll("#\\{[a-z0-9A-Z_\\$]*}", "?");
    // 执行sql
    PreparedStatement ps = null;
    ResultSet rs = null;
    Object obj = null;
    try {
        ps = connection.prepareStatement(sql);
        ps.setString(1, parameterObj.toString());
        rs = ps.executeQuery();
        if (rs.next()) {
            // 通过反射,将结果集封装对象
            String resultType = godMappedStatement.getResultType();
            Class<?> aClass = Class.forName(resultType);
            Constructor<?> con = aClass.getDeclaredConstructor();
            obj = con.newInstance();
            // 给对象obj属性赋值
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            for (int i = 1; i < columnCount+1; i++) {
                String columnName = rsmd.getColumnName(i);
                String setMethodName = "set" + columnName.toUpperCase().charAt(0) + columnName.substring(1);
                Method setMethod = aClass.getDeclaredMethod(setMethodName, aClass.getDeclaredField(columnName).getType());
                setMethod.invoke(obj, rs.getString(columnName));
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        try {
            ps.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    return obj;
}

5.3 GodBatis使用Maven打包

[记录学习]自学动力节点老杜MyBatis笔记_02_第95张图片

  • 查看本地仓库中是否已经有jar包:

    [记录学习]自学动力节点老杜MyBatis笔记_02_第96张图片

5.4 使用GodBatis

  • 使用GodBatis就和使用MyBatis是一样的。

  • 第一步:准备数据库表t_user

    [记录学习]自学动力节点老杜MyBatis笔记_02_第97张图片

  • 第二步:创建模块,普通的Java Maven模块:godbatis-test

  • 第三步:引入依赖pom.xml

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
    
        <groupId>com.powernodegroupId>
        <artifactId>godbatis-testartifactId>
        <version>1.0-SNAPSHOTversion>
        <packaging>jarpackaging>
    
        <dependencies>
            <dependency>
                <groupId>org.godgroupId>
                <artifactId>godbatisartifactId>
                <version>1.0-SNAPSHOTversion>
            dependency>
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <version>8.0.30version>
            dependency>
            <dependency>
                <groupId>ch.qos.logbackgroupId>
                <artifactId>logback-classicartifactId>
                <version>1.2.11version>
            dependency>
            <dependency>
                <groupId>junitgroupId>
                <artifactId>junitartifactId>
                <version>4.13.2version>
                <scope>testscope>
            dependency>
        dependencies>
    
        <properties>
            <maven.compiler.source>8maven.compiler.source>
            <maven.compiler.target>8maven.compiler.target>
            <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        properties>
    
    project>
    
  • 第四步:编写pojo类 User.java

    package com.powernode.godbatis.pojo;
    
    /**
     * 用户类
     */
    public class User {
        private String id;
        private String name;
        private String email;
        private String address;
    
        @Override
        public String toString() {
            return "User{" +
                    "id='" + id + '\'' +
                    ", name='" + name + '\'' +
                    ", email='" + email + '\'' +
                    ", address='" + address + '\'' +
                    '}';
        }
    
        public User() {
        }
    
        public User(String id, String name, String email, String address) {
            this.id = id;
            this.name = name;
            this.email = email;
            this.address = address;
        }
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    }
    
    
  • 第五步:编写核心配置文件:godbatis-config.xml

    
    DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <properties resource="jdbc.properties"/>
        <settings>
            <setting name="mapUnderscoreToCamelCase" value="true"/>
            <setting name="lazyLoadingEnabled" value="true"/>
        settings>
        <typeAliases>
            <package name="com.powernode.mybatis.pojo"/>
        typeAliases>
        <environments default="dev">
            <environment id="dev">
                <transactionManager type="JDBC"/>
                <dataSource type="UNPOOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                dataSource>
            environment>
        environments>
        <mappers>
            
            <mapper resource="UserMapper.xml"/>
        mappers>
    configuration>
    
  • 第六步:编写sql映射文件:UserMapper.xml

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="User">
    
        <insert id="insertUser">
            insert into t_user values(#{id}, #{name}, #{email}, #{address})
        insert>
    
        <select id="selectById" resultType="com.powernode.godbatis.pojo.User">
            select * from t_user where id = #{id}
        select>
    mapper>
    
  • 第七步:编写测试类

    package com.powernode.godbatis.test;
    
    import com.powernode.godbatis.pojo.User;
    import org.dom4j.DocumentException;
    import org.god.core.Resources;
    import org.god.core.SqlSession;
    import org.god.core.SqlSessionFactory;
    import org.god.core.SqlSessionFactoryBuilder;
    import org.junit.Test;
    
    import java.util.List;
    
    public class GodbatisTest {
    
        @Test
        public void testSelectById() throws DocumentException {
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("godbatis-config.xml"));
            SqlSession sqlSession = sqlSessionFactory.openSession();
            User user = (User) sqlSession.selectOne("User.selectById", 1);
            System.out.println(user);
            sqlSession.close();
        }
    
        @Test
        public void testInsertUser() throws DocumentException {
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("godbatis-config.xml"));
            SqlSession sqlSession = sqlSessionFactory.openSession();
            User user = new User("1", "zhangsan", "[email protected]", "北京大兴区");
            int count = sqlSession.insert("User.insertUser", user);
            System.out.println("插入了几条数据:" + count);
            sqlSession.commit();
            sqlSession.close();
        }
    }
    
  • 执行结果:

    • insertUser

    [记录学习]自学动力节点老杜MyBatis笔记_02_第98张图片

    [记录学习]自学动力节点老杜MyBatis笔记_02_第99张图片

    • selectById

    [记录学习]自学动力节点老杜MyBatis笔记_02_第100张图片

你可能感兴趣的:(学习,mybatis)