当前位置: 首页 > article >正文

三天急速通关MyBatis

三天急速通关MyBatis

  • 0 文章介绍
  • 1 介绍
  • 2 简单CURD
    • 2.1 环境准备
    • 2.2 配置文件
    • 2.3 实现CURD
    • 2.4 MyBatis的核心配置文件
    • 2.5 Tips
  • 3 MVC中的MyBatis
    • 3.1 代码结构介绍
    • 3.2 代码
    • 3.3 Tips
  • 4 技巧
    • 4.1 作用域
    • 4.2 事务
    • 4.3 Javassist生成DaoImpl类与MyBatis内置接口生成DaoImpl类
    • 4.4 两种占位符、类别名、`mappers`映射方式以及主键回显
  • 5 进阶CURD
    • 5.1 参数传递
    • 5.2 查询结果接收
    • 5.3 高级映射及延迟加载
  • 6 缓存与逆向工程
    • 6.1 缓存
    • 6.2 逆向工程
  • 7 PageHelper与注解式开发
    • 7.1 PageHelper
    • 7.2 注解式开发

0 文章介绍

在倍速观看动力节点杜老师的MyBatis教程之后,根据视频内容以及课程笔记进行实践,经过自己的理解并总结后形成这篇学习笔记。文章总共分为七个章节,包括了原教程的17个章节的知识,学习本文的前置知识需要:JavaSEJDBCMySQLXML。本文所提供的信息和内容仅供参考,作者和发布者不保证其准确性和完整性。

1 介绍

前面学习了JDBC的基本使用,并结合Druid连接池封装了一个JDBCUtil工具类,在使用的时候会发现几个问题:

  • SQl语句都是写死在DaoImpl里面的,违背了OCP
  • PreparedStatement设置参数麻烦。
  • 结果接收到POJO需要写繁琐的反射相关代码。

这个时候MyBatis出现了,是基于Java的持久层框架,将 SQL 语句和 Java 代码分离,通过配置文件或者注解的方式将它们关联起来(解决了不止上面所提到的一大堆问题)。避免在 Java代码中直接编写SQL语句,提高了代码的可维护性和可读性。

那其实就这么简单,高级一点的JDBCUtil工具类而已!是SSM之一。

2 简单CURD

跟着做就好,0 数据库表创建 -> 1 创建Maven Java Web工程 -> 2 依赖导入 -> 3 配置MyBatisXML -> 4 配置Mapper.xml文件 -> 5 编写Java代码实现CRUD

2.1 环境准备

暂时用不着TomcatMaven的打包方式设置为Jar。

  • IntelliJ IDEA:2024.1.7

  • Navicat for MySQL:17.1.2

  • MySQL:8.0.26

  • JDK:17.0.2

  • Maven:3.9.1

数据库表:t_car
在这里插入图片描述

2.2 配置文件

  • Mavenpom.xml

    • 打包方式
    <groupId>com.cut</groupId>
    <artifactId>mybatis-crud</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    
    • mybatis依赖 + mysql驱动依赖 + junit依赖
    <!--mybatis核心依赖-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.10</version>
    </dependency>
    <!--mysql驱动依赖-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.30</version>
    </dependency>
    <!-- junit依赖 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>RELEASE</version>
        <scope>compile</scope>
    </dependency>
    
  • resources根目录下创建MyBatis配置文件:mybatis-config.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/uvcut"/>
                    <property name="username" value="root"/>
                    <property name="password" value="adododes"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <!--sql映射文件创建好之后,需要将该文件路径配置到这里-->
            <mapper resource="CarMapper.xml"/>
        </mappers>
    </configuration>
    
  • resources根目录下创建t_car表的映射文件:CarMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--namespace先随意写一个-->
    <mapper namespace="car">
        <!--C: insert 插入一条数据-->
        <insert id="insertCarByMap">
            insert into t_car
                (id, car_num, brand, guide_price, produce_time, car_type)
            values (#{id}, #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType})
        </insert>
        <insert id="insertCarByPOJO">
            insert into t_car
                (id, car_num, brand, guide_price, produce_time, car_type)
            values (#{id}, #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType})
        </insert>
        <!--R: select 通过id查询一条数据-->
        <select id="selectCarByID" resultType="com.cut.mybatis.pojo.Car">
            select id, car_num as carNum, brand, guide_price as guidePrice, produce_time produceTime, car_type carType
            from t_car
            where id=#{id}
        </select>
        <select id="selectCars" resultType="com.cut.mybatis.pojo.Car">
            select id, car_num as carNum, brand, guide_price as guidePrice, produce_time produceTime, car_type carType
            from t_car
            where id > 1
        </select>
        <!--U: update 通过id更新一条数据-->
        <update id="updateByCarNum">
            update t_car
            set brand        = #{brand},
                guide_price  = #{guidePrice},
                produce_time = #{produceTime},
                car_type     = #{carType}
            where car_num = #{carNum}
        </update>
        <!--D: delete 通过carNum删除一条数据-->
        <delete id="deleteByCarNum">
            delete
            from t_car
            where car_num = #{carNum}
        </delete>
    </mapper>
    

2.3 实现CURD

创建t_carPOJO,然后再创建用于解析mybatis-config.xml并提供SQL会话的工具类,最后再创建CRUDTest类利用SqlSessionUtil实现数据库的CRUD

  • t_carPOJOCar

    package com.cut.mybatis.pojo;
    
    /**
     * @author tang
     * @version 1.0.0
     * @since 1.0.0
     */
    public class Car {
        private Long id;
        private String carNum;
        private String brand;
        private Double guidePrice;
        private String produceTime;
        private String carType;
        ... ...
    
  • 工具类SqlSessionUtil

    package com.cut.mybatis.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;
    import java.io.InputStream;
    
    /**
     * 获取SqlSession对象的工具类
     *
     * @author tang
     * @version 1.0.0
     * @since 1.0.0
     */
    public class SqlSessionUtil {
        private final static SqlSessionFactory sqlSessionFactory;
    
        static {
            // Step 1 创建SqlSessionFactoryBuilder对象
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            try {
                // Step 2 创建SqlSessionFactory对象
                InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
                sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 获取SqlSession对象,自动提交事务
         *
         * @return SqlSession对象
         */
        public static SqlSession openSession() {
            // Step 3 创建SqlSessionFactory对象
    
            return sqlSessionFactory.openSession(true);
        }
    }
    
  • 测试类CRUDTest

    package com.cut.mybatis;
    
    import com.cut.mybatis.pojo.Car;
    import com.cut.mybatis.utils.SqlSessionUtil;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.jupiter.api.Test;
    
    import java.util.HashMap;
    import java.util.List;
    
    /**
     * CRUD测试类
     *
     * @author tang
     * @version 1.0.0
     * @since 1.0.0
     */
    public class CRUDTest {
        /**
         * 测试插入操作通过Map传递值
         */
        @Test
        public void testInsertByMap() {
            SqlSession sqlSession = SqlSessionUtil.openSession();
            HashMap<String, Object> map = new HashMap<>();
            map.put("carNum", "110");
            map.put("brand", "奔驰");
            map.put("guidePrice", 30.0);
            map.put("produceTime", "2020-01-01");
            map.put("carType", "SUV");
            sqlSession.insert("insertCarByMap", map);
            sqlSession.commit();
            sqlSession.close();
        }
    
        /**
         * 测试插入操作通过POJO传递值
         */
        @Test
        public void testInsertByPOJO() {
            SqlSession sqlSession = SqlSessionUtil.openSession();
            Car car = new Car();
            car.setCarNum("110");
            car.setBrand("奔驰");
            car.setGuidePrice(30.0);
            car.setProduceTime("2020-01-01");
            car.setCarType("SUV");
            sqlSession.insert("insertCarByPOJO", car);
            sqlSession.commit();
            sqlSession.close();
        }
    
    
        /**
         * 测试删除操作
         */
        @Test
        public void testDeleteByCarNum() {
            SqlSession sqlSession = SqlSessionUtil.openSession();
            sqlSession.delete("deleteByCarNum", "110");
            sqlSession.commit();
            sqlSession.close();
        }
    
    
        /**
         * 测试更新操作
         */
        @Test
        public void testUpdateByCarNum() {
            SqlSession sqlSession = SqlSessionUtil.openSession();
            Car car = sqlSession.selectOne("selectCarByID", 1);
            car.setBrand(car.getBrand() + "已更新");
            int count = sqlSession.update("updateByCarNum", car);
            System.out.println(count != 0 ? "更新成功" : "更新失败");
            sqlSession.commit();
            sqlSession.close();
        }
    
        /**
         * 测试查询操作: 通过ID查询一个
         */
        @Test
        public void testSelectCarByID() {
            SqlSession sqlSession = SqlSessionUtil.openSession();
            Car car = sqlSession.selectOne("selectCarByID", 1);
            System.out.println(car);
            sqlSession.close();
        }
    
        /**
         * 测试查询操作: 查询所有id大于1的车
         */
        @Test
        public void testSelectCars() {
            SqlSession sqlSession = SqlSessionUtil.openSession();
            List<Car> cars = sqlSession.selectList("selectCars");
            cars.forEach(System.out::println);
            sqlSession.close();
        }
    
    
    }
    

2.4 MyBatis的核心配置文件

下面是目前会用到的标签的说明:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- 根标签 -->
<configuration>
    <!-- 1. 引入外部属性文件 -->
    <!-- 定义和引入外部属性文件中的变量。比如类路径下有个存放键值对的db.properties文件 -->
    <properties resource="db.properties">
        <!-- 也可以在这里直接定义属性 -->
        <!-- <property name="driver" value="com.mysql.cj.jdbc.Driver"/> -->
    </properties>

    <!-- 2. 环境配置 -->
    <!-- 可以配置多个数据源 -->
    <environments default="development">
        <environment id="development">
            <!-- 事务管理器: 
                type = "JDBC"利用JDBC的事务提交控制,
                type = "MANAGED"交给其它容器来管理事务,如果没有管理事务的容器,则没有事务则执行一条DML就提交一次 
			-->
            <transactionManager type="JDBC"/>
            <!-- 数据源:
				type = "UNPOOLED": 传统的获取连接的方式,实现Javax.sql.DataSource接口,没有使用池的思想
				type = "POOLED": 传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现
				type = "JNDI": 服务器提供的JNDI技术实现,不同的服务器获取不同DataSource对象,war工程才能使用。
			-->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 3. 映射器配置 -->
    <mappers>
        <!-- 映射文件方式 -->
        <mapper resource="com/example/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

2.5 Tips

  • 核心文件mybatis-config.xml的命名是随意的,读取的时候修改名字即可,位置通常放在类路径中,org.apache.ibatis.io.Resources可以读取类路径中的文件。如果不在类路径中,需要使用 java.io.FileInputStream读取。
  • CarMapper.xmlsql语句中使用#{map集合的key}来完成传值,#{}就是占位符,如果通过Map传参,当#{}内填入原Map中没有的Key不会报错,但是POJO不行,因为POJO实际是看有没有属性的Getter方法。
  • CarMapper.xml中标签的resultType属性是必要的,当查询结果的字段名和java类的属性名对应不上需用as关键字起别名。
  • CarMapper.xml中标签namespace属性可以翻译为命名空间,两个不同Mapper.xml中有相同idDML标签,需要在调用的时候加上具体的namespace

3 MVC中的MyBatis

3.1 代码结构介绍

主要完成转账的操作,需要创建t_account表:
请添加图片描述
MVC架构:Model View Controller,其中:

  • Model:包括业务层和持久层

    • dao
    • service
    • pojo
  • View:简单利用index.html展现,利用Form表单提交Post请求。

  • Controller:继承HttpServlet,重写service或者doPost方法,调用Modelservice层完成请求操作。

  • 其他:包括工具类和异常类

具体项目路径:
在这里插入图片描述

3.2 代码

0 数据库表创建 -> 1 创建Maven Java Web工程 -> 2 依赖导入 -> 3 配置MyBatisXML -> 4 配置Mapper.xml文件 -> 5 异常类 ->6 工具类 -> 7 pojo实体类 -> 8 dao层 -> 9 service层 -> 10 controller

  • 引入servlet依赖

    <!--servlet依赖-->
    <dependency>
        <groupId>jakarta.servlet</groupId>
        <artifactId>jakarta.servlet-api</artifactId>
        <version>5.0.0</version>
        <scope>provided</scope>
    </dependency>
    
  • 配置mybatis-config.xml,这里创建了db.properties

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <!--配置数据库properties参数变量-->
        <properties resource="db.properties"/>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="${driver}"/>
                    <property name="url" value="${url}"/>
                    <property name="username" value="${username}"/>
                    <property name="password" value="${password}"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <!--sql映射文件创建好之后,需要将该文件路径配置到这里-->
            <mapper resource="AccountMapper.xml"/>
        </mappers>
    
    </configuration>
    
    driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/uvcut
    username=root
    password=adododes
    
  • 配置AccountMapper.xml,两个方法就能完成转账操作

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="account">
        <select id="selectByActNo" resultType="com.cut.mybatis.pojo.Account">
            select id, act_no as actNo, balance
            from t_account
            where act_no = #{actNo}
        </select>
        <update id="updateByPOJO">
            update t_account
            set act_no  = #{actNo},
                balance = #{balance}
            where id = #{id}
        </update>
    
    </mapper>
    
  • 两个异常类:BalanceNotEnoughExceptionOtherException,工具类利用CRUD练习里面的即可

  • dao层,负责数据库的操作:

    package com.cut.mybatis.dao;
    
    import com.cut.mybatis.pojo.Account;
    
    /**
     * AccountDao接口
     *
     * @author tang
     * @version 1.0.0
     * @since 1.0.0
     */
    public interface AccountDao {
    
        /**
         * 根据账户号码查询账户
         *
         * @param actNo 账户号码
         * @return 账户
         */
        Account selectByActNo(String actNo);
    
        /**
         * 更新账户
         *
         * @param account 账户
         * @return 影响的行数
         */
        int updateByPOJO(Account account);
    }
    
    
    package com.cut.mybatis.dao.impl;
    
    import com.cut.mybatis.dao.AccountDao;
    import com.cut.mybatis.pojo.Account;
    import com.cut.mybatis.utils.SqlSessionUtil;
    import org.apache.ibatis.session.SqlSession;
    
    /**
     * @author tang
     * @version 1.0.0
     * @since 1.0.0
     */
    public class AccountDaoImpl implements AccountDao {
        @Override
        public Account selectByActNo(String actNo) {
            SqlSession sqlSession = SqlSessionUtil.openSession();
            Account account = sqlSession.selectOne("selectByActNo", actNo);
            sqlSession.close();
            return account;
        }
    
        @Override
        public int updateByPOJO(Account account) {
            SqlSession sqlSession = SqlSessionUtil.openSession();
            int count = sqlSession.update("updateByPOJO", account);
            sqlSession.commit();
            sqlSession.close();
            return count;
        }
    }
    
  • service层,负责实现具体业务逻辑

    package com.cut.mybatis.service;
    
    import com.cut.mybatis.exceptions.BalanceNotEnoughException;
    import com.cut.mybatis.exceptions.OtherException;
    
    /**
     * AccountService接口
     *
     * @author tang
     * @version 1.0.0
     * @since 1.0.0
     */
    public interface AccountService {
        /**
         * 转账操作
         *
         * @param fromActNo 转出账户
         * @param toActNo   转入账户
         * @param money     转账金额
         * @throws BalanceNotEnoughException 余额不足异常
         * @throws OtherException            其他异常
         */
        void transfer(String fromActNo, String toActNo, Double money) throws BalanceNotEnoughException, OtherException;
    }
    
    package com.cut.mybatis.service.impl;
    
    import com.cut.mybatis.dao.AccountDao;
    import com.cut.mybatis.dao.impl.AccountDaoImpl;
    import com.cut.mybatis.exceptions.BalanceNotEnoughException;
    import com.cut.mybatis.exceptions.OtherException;
    import com.cut.mybatis.service.AccountService;
    import com.cut.mybatis.pojo.Account;
    
    /**
     * AccountServiceImpl实现类
     *
     * @author tang
     * @version 1.0.0
     * @since 1.0.0
     */
    public class AccountServiceImpl implements AccountService {
        private final AccountDao accountDao = new AccountDaoImpl();
    
        @Override
        public void transfer(String fromActNo, String toActNo, Double money) throws BalanceNotEnoughException, OtherException {
            Account fromAccount = accountDao.selectByActNo(fromActNo);
            Account toAccount = accountDao.selectByActNo(toActNo);
            if (fromAccount.getBalance() < money) {
                throw new BalanceNotEnoughException("余额不足");
            }
            fromAccount.setBalance(fromAccount.getBalance() - money);
            toAccount.setBalance(toAccount.getBalance() + money);
            try {
                accountDao.updateByPOJO(fromAccount);
                accountDao.updateByPOJO(toAccount);
            } catch (Exception e) {
                throw new OtherException("其他未知异常");
            }
        }
    }
    
  • controller,负责调用service完成业务,并返回给用户视图。

    package com.cut.mybatis.web.controller;
    
    import com.cut.mybatis.exceptions.BalanceNotEnoughException;
    import com.cut.mybatis.exceptions.OtherException;
    import com.cut.mybatis.service.AccountService;
    import com.cut.mybatis.service.impl.AccountServiceImpl;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.http.HttpServlet;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    
    import java.io.IOException;
    
    /**
     * AccountController控制器
     *
     * @author tang
     * @version 1.0.0
     * @since 1.0.0
     */
    @WebServlet("/transfer")
    public class AccountController extends HttpServlet {
        private final AccountService accountService = new AccountServiceImpl();
    
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setContentType("text/html;charset=utf-8");
    
            String fromActNo = request.getParameter("fromActNo");
            String toActNo = request.getParameter("toActNo");
            Double money = Double.parseDouble(request.getParameter("money"));
            try {
                accountService.transfer(fromActNo, toActNo, money);
                response.getWriter().write("转账成功");
            } catch (BalanceNotEnoughException e) {
                response.getWriter().write("余额不足");
            } catch (OtherException e) {
                response.getWriter().write("其他异常");
            }
        }
    }
    
  • webapp下创建index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>transfer</title>
    </head>
    <body>
    <form action="/bank/transfer" method="post">
        <input type="text" name="fromActNo" placeholder="转出账户">
        <input type="text" name="toActNo" placeholder="转入账户">
        <input type="text" name="money" placeholder="转账金额">
        <input type="submit" value="转账">
    </form>
    </body>
    </html>
    

3.3 Tips

  • 浏览器最好使用无痕模式!

4 技巧

技巧方面包括:Mybatis对象作用域的说明、添加事务、利用Javassist生成DaoImpl类、使用MyBatis内部的接口创建DaoImpl类、两种占位符${}#{}的使用及区别、开启自动字段别名、Mapper映射文件的配置方式、主键回显。

4.1 作用域

  • SqlSessionFactoryBuilder
    • 作用:用于构建SqlSessionFactory。主要职责是解析MyBatis的配置文件信息创建SqlSessionFactory
    • 作用域:临时工具,完成 SqlSessionFactory 的创建就不再需要保留。
    • 最佳实践:方法内部创建 SqlSessionFactoryBuilder,使用它创建完SqlSessionFactory 后就丢弃,不然会占用XML 解析资源。
  • SqlSessionFactory
    • 作用:用于创建 SqlSession
    • 作用域:一旦创建,会一直存在于应用的生命周期中。不需要被频繁创建或销毁,全局共享。
    • 最佳实践:使用单例模式。
  • SqlSession
    • 作用用于执行 SQL 语句、管理事务等。。
    • 作用域:非线程安全,每个线程都应该有自己的 SqlSession 实例,不能共享,最佳作用域是请求或方法作用域。
    • 最佳实践:例如,在 Web 应用中,每次 HTTP 请求可以创建一个 SqlSession,在请求结束时关闭它。
  • 区分Servlet的三大作用域
    • Request Scope(请求作用域)
    • Session Scope(会话作用域)
    • Application Scope(应用作用域)
  • SqlSession工具类的理解
    • SqlSessionFactoryBuilder是在静态代码块中创建的,同样使用完后会被回收。
    • SqlSessionFactory属于工具类的静态对象,也是在静态代码块中创建了一次,后面使用的都是同一个对象。
    • SqlSession每次调用工具类的方法都会返回一个新的SqlSession,这里需要利用ThreadLocal改进原有代码。

4.2 事务

上面提到ThreadLocal改进工具类SqlSessionUtil不止能保证Session的线程安全,同时能利用其手动管理事务,保证事务的原子性。

  • SqlSessionUtil工具类的修改

    /**
     * 获取SqlSession对象
     *
     * @return SqlSession对象
     */
    public static SqlSession openSession() {
        SqlSession sqlSession = sqlSessionThreadLocal.get();
        if (sqlSession == null) {
            sqlSession = sqlSessionFactory.openSession();
            sqlSessionThreadLocal.set(sqlSession);
        }
        return sqlSession;
    }
    
    /**
     * 关闭SqlSession对象
     *
     * @param sqlSession SqlSession对象
     */
    public static void closeSession(SqlSession sqlSession) {
        if (sqlSession != null) {
            sqlSession.close();
        }
        sqlSessionThreadLocal.remove();
    }
    
  • 去掉DaoImpl的所有SqlSessioncommitclose操作

  • AccountServiceImpl的修改

    try {
        // 获取当前线程的SqlSession
        SqlSession sqlSession = SqlSessionUtil.openSession();
        accountDao.updateByPOJO(fromAccount);
        //if (fromAccount.getBalance() > 0) {
        //    throw new OtherEnoughException("手动异常");
        //}
        accountDao.updateByPOJO(toAccount);
        // 手动提交事务并关闭SqlSession
        sqlSession.commit();
        SqlSessionUtil.closeSession(sqlSession);
    } catch (Exception e) {
        throw new OtherException("其他未知异常");
    }
    

4.3 Javassist生成DaoImpl类与MyBatis内置接口生成DaoImpl类

  • Javassist的依赖添加

    <dependency>
      <groupId>org.javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.29.1-GA</version>
    </dependency>
    
  • Javassist简单使用

    package com.cut.mybatis.utils;
    
    import javassist.ClassPool;
    import javassist.CtClass;
    import javassist.CtMethod;
    import javassist.Modifier;
    
    /**
     * 尝试利用Javassist生成动态代理类
     *
     * @author tang
     * @version 1.0.0
     * @since 1.0.0
     */
    public class JavassistDemo {
        public static void main(String[] args) throws Exception {
            // 获取类池创建类
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.makeClass("com.cut.mybatis.util.Test");
    
            // 创建方法
            // 1.返回值类型 2.方法名 3.形式参数列表 4.所属类
            CtMethod ctMethod = new CtMethod(CtClass.voidType, "hello", new CtClass[]{}, ctClass);
            ctMethod.setModifiers(Modifier.PUBLIC);
            ctMethod.setBody("{System.out.println(\"hello world\");}");
            ctClass.addMethod(ctMethod);
    
            // 生成类并利用反射调用方法
            Class<?> aClass = ctClass.toClass();
            Object o = aClass.getDeclaredConstructor().newInstance();
            aClass.getDeclaredMethod("hello").invoke(o);
        }
    }
    
  • Javassist创建DaoImpl

    package com.cut.mybatis.utils;
    
    import javassist.ClassPool;
    import javassist.CtClass;
    import javassist.CtMethod;
    import javassist.Modifier;
    import org.apache.ibatis.session.SqlSession;
    
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    /**
     * DaoImpl类的生成器,利用Javassist完成
     *
     * @author tang
     * @version 1.0.0
     * @since 1.0.0
     */
    public class DaoImplGenerator {
        /**
         * 根据dao接口生成dao接口的代理对象
         * 1. daoImpl类实现dao接口
         * 2. 获取dao接口的方法签名
         * 3. 为daoImpl类生成方法体, 通过读取SqlSession对象中加载的Mapper文件内容动态生成方法体
         * 4. 创建并返回
         *
         * @param sqlSession   sql会话
         * @param daoInterface dao接口
         * @return dao接口代理对象
         */
        public static Object getMapper(SqlSession sqlSession, Class<?> daoInterface) {
            // Step 1 daoImpl类实现dao接口
            ClassPool pool = ClassPool.getDefault();
            CtClass daoImpl = pool.makeClass(daoInterface.getPackageName() + ".impl." + daoInterface.getSimpleName() + "Impl");
            CtClass ctInterface = pool.makeClass(daoInterface.getName());
            daoImpl.addInterface(ctInterface);
    
            // Step 2 获取dao接口的方法签名
            Method[] methods = daoInterface.getDeclaredMethods();
            Arrays.stream(methods).forEach(method -> {
                // Step 3 为daoImpl类生成方法体
                // Step 3.1方法签名字符串
                StringBuilder methodStr = new StringBuilder();
                methodStr.append(method.getReturnType().getName());
                methodStr.append(" ");
                methodStr.append(method.getName());
                methodStr.append("(");
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int i = 0; i < parameterTypes.length; i++) {
                    methodStr.append(parameterTypes[i].getName());
                    methodStr.append(" arg");
                    methodStr.append(i);
                    if (i != parameterTypes.length - 1) {
                        methodStr.append(",");
                    }
                }
                methodStr.append("){");
                // Step 3.2方法体名字符串
                String sqlId = daoInterface.getName() + "." + method.getName();
                // 获取DML语句类型
                String DMLType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType().name();
                String sqlSessionPath = "org.apache.ibatis.session.SqlSession";
                String sqlSessionUtilPath = "com.cut.mybatis.utils.SqlSessionUtil";
                if ("SELECT".equals(DMLType)) {
                    methodStr.append("%s sqlSession = %s.openSession();".formatted(sqlSessionPath, sqlSessionUtilPath));
                    methodStr.append("Object obj = sqlSession.selectOne(\"%s\", arg0);".formatted(sqlId));
                    methodStr.append("return (%s)obj;".formatted(method.getReturnType().getName()));
                } else if ("UPDATE".equals(DMLType)) {
                    methodStr.append("%s sqlSession = %s.openSession();".formatted(sqlSessionPath, sqlSessionUtilPath));
                    methodStr.append("int count = sqlSession.update(\"%s\", arg0);".formatted(sqlId));
                    methodStr.append("return count;");
                }
                methodStr.append("}");
                System.out.println(methodStr);
                // Step 3.3将方法体字符串添加到方法中
                try {
                    CtMethod ctMethod = CtMethod.make(methodStr.toString(), daoImpl);
                    ctMethod.setModifiers(Modifier.PUBLIC);
                    daoImpl.addMethod(ctMethod);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
            // Step 4 创建并返回
            try {
                // 创建代理对象
                Class<?> daoImplClass = daoImpl.toClass();
                return daoImplClass.getDeclaredConstructor().newInstance();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    
  • 修改serviceImpl

    // private final AccountDao accountDao = new AccountDaoImpl();
    private final AccountDao accountDao = ((AccountDao)DaoImplGenerator.getMapper(SqlSessionUtil.openSession(), AccountDao.class));
    
  • Mybatis自带的DaoImpl类生成的接口

    // private final AccountDao accountDao = new AccountDaoImpl();
    // private final AccountDao accountDao = ((AccountDao)DaoImplGenerator.getMapper(SqlSessionUtil.openSession(), AccountDao.class));
    private final AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class);
    

4.4 两种占位符、类别名、mappers映射方式以及主键回显

  • 两种#{}与${}的区别

    • 预处理占位符(PreparedStatement):

      #{} MyBatis 中用于参数绑定的占位符。将参数值通过 JDBCPreparedStatement 进行预处理,即在 SQL 执行之前,将参数值绑定到 SQL 语句中。

    • 字符串替换占位符:

      ${}MyBatis 中用于字符串替换的占位符。它会在 SQL 语句被解析时,直接将变量的值替换到 SQL 中,而不是通过预处理的方式。

  • 类别名,在mybatis-config.xml中配置

    <typeAliases>
      <typeAlias type="com.powernode.mybatis.pojo.Car" alias="Car"/>
    </typeAliases>
    

    其中:

    • alias属性不是必须的,如果缺省的话,type属性指定的类型名的简类名作为别名。
    • alias是大小写不敏感的。也就是说假设alias="Car",再用的时候,可以CAR,也可以car,也可以Car,都行。

    如果类太多,使用package,将该包下的所有类都自动起别名

    <typeAliases>
      <package name="com.powernode.mybatis.pojo"/>
    </typeAliases>
    
  • mappers映射文件的配置方式

    • resource:类路径加载XML 文件,通常用于加载位于 src/main/resources 或其他类路径下的 XML 文件

      <mappers>
          <mapper resource="mappers/UserMapper.xml"/>
      </mappers>
      
    • url:全限定资源路径中加载XML 文件,可以加载位于文件系统或其他位置的 XML 文件。

      <mappers>
          <mapper url="file:///path/to/mappers/UserMapper.xml"/>
      </mappers>
      
    • class:指定 Mapper 接口的完全限定类名,适用于使用注解定义 Mapper 的情况,MyBatis 会自动扫描该接口并注册。

      package com.example.mapper;
      
      @Mapper
      public interface UserMapper {
          @Select("SELECT * FROM users WHERE id = #{id}")
          User selectUserById(int id);
      }
      
      <mappers>
          <mapper class="com.example.mapper.UserMapper"/>
      </mappers>
      
    • package:指定一个包路径,MyBatis 会自动扫描该包及其子包下的所有接口,并将它们注册为 Mapper

      <mappers>
          <package name="com.example.mapper"/>
      </mappers>
      
  • 主键回显

    当用户插入一条新的记录之后,自动生成了主键,而这个主键需要在其他表中使用时就可以设置。

    <insert id="insertUseGeneratedKeys" useGeneratedKeys="true" keyProperty="id">
    	<!-- 插入DML -->
    </insert>
    
    // 这里就不用insert,而用insertUseGeneratedKeys
    mapper.insertUseGeneratedKeys(car);
    

5 进阶CURD

进阶的CRUD考虑到了传递参数的不同,以及查询结果的不同,以及DML语句的动态构建。

5.1 参数传递

  • 单个简单参数,简单参数包括下面这些,MyBatis会自动进行类型推断

    • byte short int long float double char
    • Byte Short Integer Long Float Double Character
    • String
    • java.util.Date
    • java.sql.Date
  • MapPOJO实体类参数

    • Map:手动封装Map集合,将每个条件以keyvalue的形式存放到集合中。然后在使用的时候通过#{map集合的key}来取值。
    • POJO#{} 里面写的是属性名字。这个属性名其本质上是:set/get方法名去掉set/get之后的名字。
  • 多参数

    多参数传递,MyBatis会默认将参数封装成Map的形式,取值方式:#{arg0} ,#{arg1} ,值得注意的是arg0 = param1,arg1 = param2。相当于有两份。

  • @Param注解

        /**
         * 根据name和age查询
         * @param name
         * @param age
         * @return
         */
        List<Student> selectByNameAndAge(@Param(value="name") String name, @Param("age") int age);
    
    <select id="selectByNameAndAge" resultType="student">
      select * from t_student where name = #{name} and age = #{age}
    </select>
    

    这里除了用注解标注的#{name},#{age}以外,还可以用#{param1} ,#{param2},但是#{arg0} ,#{arg1}却不行。

5.2 查询结果接收

主要分为单个POJO,列表POJO,单个Map,列表Map,整体Map四个情况的测试

  • 创建AccountMapperExtra.xml,注意类名得是全限定类名

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.cut.mybatis.mapper.AccountMapper">
        <select id="selectById" resultType="Account">
            select id, act_no as actNo, balance
            from t_account
            where id = #{id}
        </select>
        <select id="selectAll" resultType="Account">
            select id, act_no as actNo, balance
            from t_account
        </select>
        <select id="selectByIdRetMap" resultType="map">
            select id, act_no as actNo, balance
            from t_account
            where id = #{id}
        </select>
        <select id="selectAllRetListMap" resultType="map">
            select id, act_no as actNo, balance
            from t_account
        </select>
        <select id="selectAllRetMap" resultType="map">
            select id, act_no as actNo, balance
            from t_account
        </select>
    </mapper>
    
  • mybatis-config.xml配置新增的映射文件

    <mappers>
        <!--sql映射文件创建好之后,需要将该文件路径配置到这里-->
        <mapper resource="AccountMapper.xml"/>
        <mapper resource="AccountMapperExtra.xml"/>
    </mappers>
    
  • 新建mapper包,并添加AccountMapper接口,整体类名得添加注解@MapKey,注明Mapkey

    package com.cut.mybatis.mapper;
    
    import com.cut.mybatis.pojo.Account;
    import org.apache.ibatis.annotations.MapKey;
    
    import java.util.List;
    import java.util.Map;
    
    /**
     * @author tang
     * @version 1.0.0
     * @since 1.0.0
     */
    public interface AccountMapper {
        /**
         * 根据ID查询账户信息
         *
         * @param id 账户ID
         * @return 账户信息
         */
        Account selectById(int id);
    
        /**
         * 查询所有账户信息
         *
         * @return 所有账户信息列表
         */
        List<Account> selectAll();
    
        /**
         * 根据ID查询账户信息并返回Map
         *
         * @param id 账户ID
         * @return 账户信息Map
         */
        Map<String, Object> selectByIdRetMap(int id);
    
        /**
         * 查询所有账户信息并返回List<Map>
         *
         * @return 所有账户信息的List<Map>
         */
        List<Map<String, Object>> selectAllRetListMap();
    
        /**
         * 查询所有账户信息并返回Map
         *
         * @return 所有账户信息Map
         */
        @MapKey("id")
        Map<Long,Map<String,Object>> selectAllRetMap();
    }
    
  • 编写测试类

    package com.cut;
    
    import com.cut.mybatis.mapper.AccountMapper;
    import com.cut.mybatis.pojo.Account;
    import com.cut.mybatis.utils.SqlSessionUtil;
    import org.junit.jupiter.api.Test;
    
    import java.util.List;
    import java.util.Map;
    
    /**
     * @author tang
     * @version 1.0.0
     * @since 1.0.0
     */
    public class ResultTest {
        @Test
        public void testSinglePOJO() {
            System.out.println("---------- testSinglePOJO ----------");
            AccountMapper mapper = SqlSessionUtil.openSession().getMapper(AccountMapper.class);
            Account account = mapper.selectById(1);
            System.out.println(account);
        }
    
        @Test
        public void testListPOJO() {
            System.out.println("---------- testListPOJO ----------");
            AccountMapper mapper = SqlSessionUtil.openSession().getMapper(AccountMapper.class);
            List<Account> accounts = mapper.selectAll();
            accounts.forEach(System.out::println);
        }
    
        @Test
        public void testSelectByIdRetMap() {
            System.out.println("---------- testSelectByIdRetMap ----------");
            AccountMapper mapper = SqlSessionUtil.openSession().getMapper(AccountMapper.class);
            Map<String, Object> map = mapper.selectByIdRetMap(1);
            System.out.println(map);
        }
    
        @Test
        public void testSelectAllRetListMap() {
            System.out.println("---------- testSelectAllRetListMap ----------");
            AccountMapper mapper = SqlSessionUtil.openSession().getMapper(AccountMapper.class);
            List<Map<String, Object>> maps = mapper.selectAllRetListMap();
            maps.forEach(System.out::println);
        }
    
        @Test
        public void testSelectAllRetMap() {
            System.out.println("---------- testSelectAllRetMap ----------");
            AccountMapper mapper = SqlSessionUtil.openSession().getMapper(AccountMapper.class);
            Map<Long, Map<String, Object>> maps = mapper.selectAllRetMap();
            maps.forEach((k, v) -> {
                System.out.println(k + "=" + v);
            });
        }
    }
    
  • 测试结果输出

    ---------- testSelectAllRetListMap ----------
    {balance=43000.00, actNo=act001, id=1}
    {balance=7000.00, actNo=act002, id=2}
    ---------- testSelectAllRetMap ----------
    1={balance=43000.00, actNo=act001, id=1}
    2={balance=7000.00, actNo=act002, id=2}
    ---------- testSinglePOJO ----------
    Account{id=1, actNo='act001', balance=43000.0}
    ---------- testSelectByIdRetMap ----------
    {balance=43000.00, actNo=act001, id=1}
    ---------- testListPOJO ----------
    Account{id=1, actNo='act001', balance=43000.0}
    Account{id=2, actNo='act002', balance=7000.0}
    
    Process finished with exit code 0
    

还可以进行自定义结果Map,避免查询的时候给字段其别名

  • Mapper.xml文件中新增

    <!--
        resultMap:
            id:这个结果映射的标识,作为select标签的resultMap属性的值。
            type:结果集要映射的类。可以使用别名。
    -->
    <resultMap id="accountResultMap" type="account">
        <!--对象的唯一标识,官方解释是:为了提高mybatis的性能。建议写上。-->
        <id property="id" column="id"/>
        <result property="actNo" column="act_no"/>
        <!--当属性名和数据库列名一致时,可以省略。但建议都写上。-->
        <!--javaType用来指定属性类型。jdbcType用来指定列类型。一般可以省略。-->
        <result property="balance" column="balance" javaType="Double" jdbcType="DECIMAL"/>
    </resultMap>
    
    <!--resultMap属性的值必须和resultMap标签中id属性值一致。-->
    <select id="selectAllByResultMap" resultMap="accountResultMap">
        select *
        from t_account
    </select>
    
  • 其他文件自己添加即可,不再给出

除此以外,还可以在mybatis-config.xml中添加全局设置

<!--放在properties标签后面-->
<settings>
  <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

注意命名规范!

5.3 高级映射及延迟加载

考虑学生表和班级表

  • 多对一:以学生为主表

    学生POJO中添加班级字段后,有三种方式实现查询:

    • 通过级联属性

      <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 join t_clazz c on s.cid = c.cid where sid = #{sid}
      </select>
      
    • 通过association标签

      <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>
      
    • 分布查询

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

      注意ClazzMapper接口中得声明selectByCid方法,并在ClazzMapper.xml中进行配置

      <mapper namespace="com.powernode.mybatis.mapper.ClazzMapper">
          <select id="selectByCid" resultType="Clazz">
              select * from t_clazz where cid = #{cid}
          </select>
      </mapper>
      
  • 一对多:以班级为主表

    班级POJO添加学生列表字段,主要有两种实现方式

    • collection

      <resultMap id="clazzResultMap" type="Clazz">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
        <collection property="stus" ofType="Student">
          <id property="sid" column="sid"/>
          <result property="sname" column="sname"/>
        </collection>
      </resultMap>
      
      <select id="selectClazzAndStusByCid" resultMap="clazzResultMap">
        select * from t_clazz c join t_student s on c.cid = s.cid where c.cid = #{cid}
      </select>
      
    • 分布查询

      <resultMap id="clazzResultMap" type="Clazz">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
        <!--主要看这里-->
        <collection property="stus"
                    select="com.powernode.mybatis.mapper.StudentMapper.selectByCid"
                    column="cid"/>
      </resultMap>
      
      <!--sql语句也变化了-->
      <select id="selectClazzAndStusByCid" resultMap="clazzResultMap">
        select * from t_clazz c where c.cid = #{cid}
      </select>
      

      同理StudentMapper接口中得声明selectByCid方法,并在ClazzMapper.xml中进行配置

  • 延迟加载

    就是分步查询的时候,可以先只进行一部分,当访问到需要分布查询的结果时再执行后续查询操作,两种实现方式。

    • collectionassociation设置属性fetchType="lazy"

    • mybatis-config.xml中进行全局配置

      <settings>
       	<!-- 启用延迟加载 -->
          <setting name="lazyLoadingEnabled" value="true"/>
      </settings>
      

      这个时候如果想要局部不进行延迟加载需要设置属性fetchType="eager"

6 缓存与逆向工程

6.1 缓存

缓存主要的作用是通过减少IO的方式,来提高程序的执行效率,MyBatis中是将select语句的查询结果放到缓存(内存)当中,下一次还是这条select语句的话,直接从缓存中取,不再查数据库。

  • 一级缓存:将查询到的数据存储到SqlSession中,默认开启。

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

    一级缓存失效的两种情况

    • 第一次查询和第二次查询之间,手动清空了一级缓存。

      sqlSession.clearCache();
      
    • 第一次查询和第二次查询之间,执行了增删改操作(无论哪张表,但得是同一个对象)。

  • 二级缓存:将查询到的数据存储到SqlSessionFactory中。

    MyBatis 的二级缓存是基于 Mapper 级别的全局缓存机制,用于在多个 SqlSession 实例之间共享查询结果。二级缓存需要将对象序列化,所有被缓存的对象必须实现 Serializable 接口。

    <!-- 在 MyBatis 的全局配置文件 mybatis-config.xml 中开启二级缓存支持 -->
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
    <!-- 在需要使用二级缓存的 Mapper XML 文件中添加 <cache> 标签 -->
    <mapper namespace="com.example.mapper.UserMapper">
        <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
    </mapper>
    

    其中属性的说明:

    • eviction

      • LRU:Least Recently Used。最近最少使用。优先淘汰在间隔时间内使用频率最低的对象
      • FIFO:First In First Out。一种先进先出的数据缓存器。
      • SOFT:软引用。淘汰软引用指向的对象。具体算法和JVM的垃圾回收算法有关。
      • WEAK:弱引用。淘汰弱引用指向的对象。具体算法和JVM的垃圾回收算法有关。
    • flushInterval

      二级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存,只要内存足够大,一直会向二级缓存中缓存数据。除非执行了增删改。

    • readOnly

      • true:相同sql语句执行之后返回的对象是共享的同一个,性能好,但是多线程并发可能会存在安全问题。
      • false:相同的sql语句执行之后返回的对象是副本,调用了clone方法,性能一般。但安全。
    • size

      设置二级缓存中最多可存储的java对象数量,默认值1024。

  • 三级(假三级,二级平替)缓存:MyBatis自身不直接提供,需集成其它第三方的缓存:EhCache(java)、Memcache(C)。

​ 后续的使用中再进行测试。

6.2 逆向工程

实际上就是根据数据库表逆向生成Javapojo类,SqlMapper.xml文件,以及Mapper接口类等。

Maven添加逆向工程插件 -> 配置插件执行信息 -> 点击插件运行

  • pom.xml添加插件

    <!--定制构建过程-->
    <build>
      <!--可配置多个插件-->
      <plugins>
        <!--其中的一个插件:mybatis逆向工程插件-->
        <plugin>
          <!--插件的GAV坐标-->
          <groupId>org.mybatis.generator</groupId>
          <artifactId>mybatis-generator-maven-plugin</artifactId>
          <version>1.4.1</version>
          <!--允许覆盖-->
          <configuration>
            <overwrite>true</overwrite>
          </configuration>
          <!--插件的依赖-->
          <dependencies>
            <!--mysql驱动依赖-->
            <dependency>
              <groupId>mysql</groupId>
              <artifactId>mysql-connector-java</artifactId>
              <version>8.0.30</version>
            </dependency>
          </dependencies>
        </plugin>
      </plugins>
    </build>
    
  • 配置generatorConfig.xml,放在类路径下,名字不能修改

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE generatorConfiguration
            PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
            "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    
    <generatorConfiguration>
        <!--
            targetRuntime有两个值:
                MyBatis3Simple:生成的是基础版,只有基本的增删改查。
                MyBatis3:生成的是增强版,除了基本的增删改查之外还有复杂的增删改查。
        -->
        <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/uvcut"
                            userId="root"
                            password="adododes">
            </jdbcConnection>
    
            <!-- 生成pojo包名和位置 -->
            <javaModelGenerator targetPackage="com.cut.mybatis.pojo" targetProject="src/main/java">
                <!--是否开启子包-->
                <property name="enableSubPackages" value="true"/>
                <!--是否去除字段名的前后空白-->
                <property name="trimStrings" value="true"/>
            </javaModelGenerator>
    
            <!-- 生成SQL映射文件的包名和位置 -->
            <sqlMapGenerator targetPackage="com.cut.mybatis.mapper" targetProject="src/main/resources">
                <!--是否开启子包-->
                <property name="enableSubPackages" value="true"/>
            </sqlMapGenerator>
    
            <!-- 生成Mapper接口的包名和位置 -->
            <javaClientGenerator
                    type="xmlMapper"
                    targetPackage="com.cut.mybatis.mapper"
                    targetProject="src/main/java">
                <property name="enableSubPackages" value="true"/>
            </javaClientGenerator>
    
            <!-- 表名和对应的实体类名-->
            <table tableName="t_car" domainObjectName="Car"/>
    
        </context>
    </generatorConfiguration>
    
  • 点击运行会创建四个文件

    • pojo.Car
    • pojo.CarExample 用于QBC风格查询
    • mapper.CarMapper
    • resource/com/cut/mybatis/mapper/CarMapper.xml
  • 测试环节

    • pojo.Car中重写下toString方法,方便等会儿打印测试

    • mybatis-config.xml中配置映射文件

      <mappers>
          <!--sql映射文件创建好之后,需要将该文件路径配置到这里-->
          <mapper resource="AccountMapper.xml"/>
          <mapper resource="AccountMapperExtra.xml"/>
          <mapper resource="com/cut/mybatis/mapper/CarMapper.xml"/>
      </mappers>
      
    • 编写测试类

      package com.cut;
      
      import com.cut.mybatis.mapper.CarMapper;
      import com.cut.mybatis.pojo.Car;
      import com.cut.mybatis.pojo.CarExample;
      import com.cut.mybatis.utils.SqlSessionUtil;
      import org.junit.jupiter.api.Test;
      
      import java.math.BigDecimal;
      import java.util.List;
      
      /**
       * @author tang
       * @version 1.0.0
       * @since 1.0.0
       */
      public class GeneratorTest {
          @Test
          public void testGenerator() throws Exception {
              CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
              // 查一个
              Car car = mapper.selectByPrimaryKey(1L);
              System.out.println(car);
      
              // 查所有,这里的null实际就是不加任何限制
              List<Car> cars = mapper.selectByExample(null);
              cars.forEach(System.out::println);
      
              // 多条件查询
              // QBC 风格:Query By Criteria 一种查询方式,比较面向对象,看不到sql语句。
              CarExample carExample = new CarExample();
              carExample.createCriteria()
                      .andBrandEqualTo("丰田霸道")
                      .andGuidePriceGreaterThan(new BigDecimal(60.0));
              carExample.or().andProduceTimeBetween("2000-10-11", "2022-10-11");
      
              mapper.selectByExample(carExample);
          }
      }
      
      Car{id=1, carNum='100', brand='宝马520Li已更新已更新', guidePrice=41.00, produceTime='2022-09-01', carType='燃油车'}
      Car{id=13, carNum='110', brand='奔驰', guidePrice=30.00, produceTime='2020-01-01', carType='SUV'}
      Car{id=9, carNum='104', brand='奥迪A6L', guidePrice=45.60, produceTime='2020-10-01', carType='燃油车'}
      Car{id=8, carNum='103', brand='奔驰E300L', guidePrice=50.30, produceTime='2020-10-01', carType='燃油车'}
      Car{id=2, carNum='102', brand='比亚迪汉', guidePrice=30.23, produceTime='2018-09-10', carType='电车'}
      Car{id=1, carNum='100', brand='宝马520Li已更新已更新', guidePrice=41.00, produceTime='2022-09-01', carType='燃油车'}
      
      Process finished with exit code 0
      

      对于QBC风格的评价是很难评价。

7 PageHelper与注解式开发

主要包括了PageHelper获取数据库查询结果的分页相关信息,以及注解式开发的示例

7.1 PageHelper

  • pom.xml引入依赖

    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>5.3.1</version>
    </dependency>
    
  • mybatis-config.xml配置

    <plugins>
      <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>
    
  • java中代码

    // 开启分页
    PageHelper.startPage(2, 2);
    // 执行查询语句
    List<Car> cars = mapper.selectAll();
    // 获取分页信息对象
    PageInfo<Car> pageInfo = new PageInfo<>(cars, 5);
    System.out.println(pageInfo);
    

7.2 注解式开发

不推荐使用

package com.example.mapper;

import com.example.model.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

@Mapper
public interface UserMapper {
    // 插入用户
    @Insert("INSERT INTO users (username, email, age) VALUES (#{username}, #{email}, #{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insertUser(User user);

    // 根据 ID 查询用户
    @Select("SELECT * FROM users WHERE id = #{id}")
    User selectUserById(Integer id);

    // 查询所有用户
    @Select("SELECT * FROM users")
    List<User> selectAllUsers();

    // 更新用户
    @Update("UPDATE users SET username = #{username}, email = #{email}, age = #{age} WHERE id = #{id}")
    int updateUser(User user);

    // 删除用户
    @Delete("DELETE FROM users WHERE id = #{id}")
    int deleteUserById(Integer id);
}

http://www.kler.cn/a/538129.html

相关文章:

  • redis高级数据结构Stream
  • trimesh 加载obj mesh处理
  • 人工智能A*算法与CNN结合- CNN 增加卷积层的数量,并对卷积核大小进行调整
  • Listener监听器和Filter过滤器
  • 【个人开发】macbook m1 Lora微调qwen大模型
  • 让文物“活”起来,以3D数字化技术传承文物历史文化!
  • c语言样式主题 清爽风格 代码色彩 keil风格 适合单片机开发GD32 STM32等 cursor或者vscode 的settings.json文件
  • Python第三方库复制到另一台电脑保持安装环境一致
  • 【RK3588嵌入式图形编程】-SDL2-创建应用事件循环
  • 【DeepSeek-01】无需 Docker,千元级电脑本地部署 Ollama + DeepSeek-R1:14b + Chatbox
  • stm32蓝牙模块
  • 【Pytorch实战教程】让数据飞轮转起来:PyTorch Dataset与Dataloader深度指南
  • Linux网卡配置方法
  • 02为什么 OD门和 OC门输出必须加上拉电阻?
  • 机器学习-关于线性回归的表示方式和矩阵的基本运算规则
  • 【STM32F1】一种使用通用定时器实现各个通道独立输出不同指定数量脉冲的方法
  • 掌握内容中台与人工智能技术的新闻和应用场景分析
  • vue的响应式原理以及Vue 3.0在响应式原理上的优化方案
  • Blocked aria-hidden on an element because its descendant retained focus.
  • CASAIM与马来西亚 Perodua汽车达成合作,共推汽车制造质量升级
  • 【C++八股】const和define的区别
  • 在亚马逊云科技上云原生部署DeepSeek-R1模型(下)
  • Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘(上)
  • 基于HTML5 Canvas 的盖楼游戏
  • 大数据治理新纪元:全面解读前沿开源技术的力量
  • DeepSeek——DeepSeek模型部署实战