三天急速通关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个章节的知识,学习本文的前置知识需要:JavaSE
,JDBC
,MySQL
,XML
。本文所提供的信息和内容仅供参考,作者和发布者不保证其准确性和完整性。
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 配置MyBatis
的XML
-> 4 配置Mapper.xml
文件 -> 5 编写Java
代码实现CRUD
。
2.1 环境准备
暂时用不着Tomcat
,Maven
的打包方式设置为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 配置文件
-
Maven
的pom.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_car
的POJO
,然后再创建用于解析mybatis-config.xml
并提供SQL
会话的工具类,最后再创建CRUDTest
类利用SqlSessionUtil
实现数据库的CRUD
。
-
t_car
的POJO
:Car
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.xml
的sql
语句中使用#{map集合的key}
来完成传值,#{}
就是占位符,如果通过Map
传参,当#{}
内填入原Map
中没有的Key
不会报错,但是POJO
不行,因为POJO
实际是看有没有属性的Getter
方法。CarMapper.xml
中标签的resultType
属性是必要的,当查询结果的字段名和java
类的属性名对应不上需用as
关键字起别名。CarMapper.xml
中标签namespace
属性可以翻译为命名空间,两个不同Mapper.xml
中有相同id
的DML
标签,需要在调用的时候加上具体的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
方法,调用Model
的service
层完成请求操作。 -
其他:包括工具类和异常类
具体项目路径:
3.2 代码
0 数据库表创建 -> 1 创建Maven Java Web
工程 -> 2 依赖导入 -> 3 配置MyBatis
的XML
-> 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>
-
两个异常类:
BalanceNotEnoughException
和OtherException
,工具类利用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
的所有SqlSession
的commit
和close
操作 -
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
中用于参数绑定的占位符。将参数值通过JDBC
的PreparedStatement
进行预处理,即在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
-
Map
和POJO
实体类参数Map
:手动封装Map
集合,将每个条件以key
和value
的形式存放到集合中。然后在使用的时候通过#{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
,注明Map
的key
。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
中进行配置
-
-
延迟加载
就是分步查询的时候,可以先只进行一部分,当访问到需要分布查询的结果时再执行后续查询操作,两种实现方式。
-
collection
或association
设置属性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 逆向工程
实际上就是根据数据库表逆向生成Java
的pojo
类,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);
}