正文开始
前段时间动力节点杜老师发布了最新的MyBatis视频,相比大家也是等了好久,前些时间一直没有时间,正好国庆放假几天时间跟着杜老师一起把MyBatis的知识巩固了一遍!!!为了以后更好的复习,于是简单写了一篇博客,为了更好的知识储备,也希望可以帮助到有需要的同学哈!!!
第一章 框架概述
1.1 框架
●在⽂献中看到的framework被翻译为框架
● Java常⽤框架:
○ SSM三⼤框架:Spring + SpringMVC + MyBatis
○ SpringBoot
○ SpringCloud
○ 等。。
● 框架其实就是对通⽤代码的封装,提前写好了⼀堆接⼝和类,我们可以在做项⽬的时候直接引⼊这些
接⼝和类(引⼊框架),基于这些现有的接⼝和类进⾏开发,可以⼤⼤提⾼开发效率。
● 框架⼀般都以jar包的形式存在。(jar包中有class⽂件以及各种配置⽂件等。)
● SSM三⼤框架的学习顺序:
○ ⽅式⼀:MyBatis、Spring、SpringMVC(建议)
○ ⽅式⼆:Spring、MyBatis、SpringMVC
软件开发常用结构
三层架构
三层架构包含的三层:
界面层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)
三层的职责
- 界面层(表示层,视图层) : 主要功能是接受用户的数据,显示请求的处理结果。使用 web 页面和用户交互,手机 app 也就是表示层的,用户在 app 中操作,业务逻辑在服务器端处理。
- 业务逻辑层:接收表示传递过来的数据,检查数据,计算业务逻辑,调用数据访问层获取数据。
- 数据访问层:与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交给业务层,同时将业务层处理的数据保存到数据库.
三层的处理请求的交互:
客户端<—>界面层<—>业务逻辑层<—>数据访问层<—>数据库。
如图 :
为什么要使用三层架构 ?
1、 结构清晰、耦合度低、各层分工明确、
2、 可维护度高,可扩展性高。
3、有利用标准化。
4、 开发人员可以只关注整个结构中的其中某一层的功能实现
5、有利于各层逻辑的复用
常用框架
常见的 J2EE 中开发框架
框架是什么
框架定义
框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种认为,框架是可被应用开发者定制的应用骨架、模板。
简单的说,框架其实是半成品软件,就是一组组件,供你使用完成你自己的系统。从另一个角度来说框架一个舞台,你在舞台上做表演。在框架基础上加入你要完成的功能。
框架安全的,可复用的,不断升级的软件。
框架解决的问题
框架要解决的最重要的一个问题是技术整合,在 J2EE 的 框架中,有着各种各样的技术,不同的应用,系统使用不同的技术解决问题。需要从 J2EE 中选择不同的技术,而技术自身的复杂性,有导致更大的风险。企业在开发软件项目时,主要目的是解决业务问题。 即要求企业负责技术本身,又要求解决业务问题。这是大多数企业不能完成的。框架把相关的技术融合在一起,企业开发可以集中在业务领域方面。
另一个方面可以提供开发的效率。
JDBC编程
使用JDBC的编程回顾
public void findStudent() {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//注册 mysql 驱动
Class.forName("com.mysql.jdbc.Driver");
//连接数据的基本信息 url ,username,password
String url = "jdbc:mysql://localhost:3306/springdb";
String username = "root";
String password = "123456";
//创建连接对象
conn = DriverManager.getConnection(url, username, password);
//保存查询结果
List<Student> stuList = new ArrayList<>();
//创建 Statement, 用来执行 sql 语句
stmt = conn.createStatement();
//执行查询,创建记录集,
rs = stmt.executeQuery("select * from student");
while (rs.next()) {
Student stu = new Student();
stu.setId(rs.getInt("id"));
stu.setName(rs.getString("name"));
stu.setAge(rs.getInt("age"));
//从数据库取出数据转为 Student 对象,封装到 List 集合
PreparedStatement ps = conn.prepareStatement(sql);
// 繁琐的赋值:思考⼀下,这种有规律的代码能不能通过反射机制来做⾃动化。
ps.setString(1, "1");
ps.setString(2, "123456789");
ps.setString(3, "zhangsan");
ps.setString(4, "123456");
ps.setString(5, "1980-10-11");
ps.setString(6, "男");
ps.setString(7, "zhangsan@126.com");
ps.setString(8, "北京");
ps.setString(9, "⼤兴区凉⽔河⼆街");
ps.setString(10, "1000000");
ps.setString(11, "16398574152");
ps.setString(12, "A");
// 执⾏SQL
int count = ps.executeUpdate();
// ......
stuList.add(stu);}
}catch(Exception e){
e.printStackTrace();
}finally{
try{
if(rs != null)
rs.lose();
if(pstm != null)
pstm.close();
if(con != null)
con.close();
}catch(Exception e){
e.printStackTrace();
}
}
使用JDBC的缺陷
- 代码比较多,开发效率比较低。
- SQL语句写死在Java程序中,不灵活。改SQL的话就要改Java代码。违背开闭原则OCP。
- 需要关注Connection,Statement,ResultSet对象的创建和销毁。
- 对ResultSet的查询结果,需要自己去封装为List。
- 重复冗余的代码比较多。
- 业务代码和数据库的操作混在一起。
了解MyBatis
MyBatis本质上就是对JDBC的封装,通过MyBatis完成CRUD。
● MyBatis在三层架构中负责持久层的,属于持久层框架。
● MyBatis的发展历程:【引⽤百度百科】
○ MyBatis本是apache的⼀个开源项⽬iBatis,2010年这个项⽬由apache software foundation迁
移到了google code,并且改名为MyBatis。2013年11⽉迁移到Github。
○ iBATIS⼀词来源于“internet”和“abatis”的组合,是⼀个基于Java的持久层框架。iBATIS提供的
持久层框架包括SQL Maps和Data Access Objects(DAOs)。
● 打开mybatis代码可以看到它的包结构中包含:ibatis
-
ORM : 对象关系映射
○ O(Object):Java虚拟机中的Java对象
○ R(Relational):关系型数据库
○ M(Mapping):将Java虚拟机中的Java对象映射到数据库表中⼀⾏记录,或是将数据库表中
⼀⾏记录映射成Java虚拟机中的⼀个Java对象。
○ ORM图示
下面一张图很清晰的介绍什么是ORM
- MyBatis属于半自动化ORM框架
- Hibernate全自动化ORM框架
-
MyBatis框架的优点 :
○ ⽀持定制化 SQL、存储过程、基本映射以及⾼级映射
○ 避免了⼏乎所有的 JDBC 代码中⼿动设置参数以及获取结果集
○ ⽀持XML开发,也⽀持注解式开发。【为了保证sql语句的灵活,所以mybatis⼤部分是采⽤
XML⽅式开发。】
○ 将接⼝和 Java 的 POJOs(Plain Ordinary Java Object,简单普通的Java对象)映射成数据库中的
记录
○ 体积⼩好学:两个jar包,两个XML配置⽂件。
○ 完全做到sql解耦合。
○ 提供了基本映射标签。
○ 提供了⾼级映射标签。
○ 提供了XML标签,⽀持动态SQL的编写。
○ …
MyBatis框架解决的主要问题
减轻使用 JDBC 的复杂性,不用编写重复的创建 Connetion , Statement ; 不用编写关闭资源代码。直接使用 java 对象,表示结果数据。让开发者专注
SQL的处理。 其他分心的工作由 MyBatis 代劳。MyBatis 可以完成:
注册数据库的驱动,例如 Class.forName(“com.mysql.jdbc.Driver”))
创建 JDBC 中必须使用的 Connection , Statement, ResultSet 对象
从 xml 中获取 sql,并执行 sql 语句,把 ResultSet 结果转换 java 对象
List<Student> list = new ArrayLsit<>(); ResultSet rs = state.executeQuery(“select * from student”); while(rs.next){ Student student = new Student(); student.setName(rs.getString(“name”)); student.setAge(rs.getInt(“age”)); list.add(student); } 4.关闭资源 ResultSet.close() , Statement.close() , Conenection.close()
MyBatis框架的概述
1、
mybatis配置mybatis-config.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。
mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。
2、 通过mybatis环境等配置信息构造
SqlSessionFactory即会话工厂3、 由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。
4、
mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。5、
Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。6、
Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。7、 Mapped Statement对sql执行输出结果进行定义,包括
HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
第二章 MyBatis框架快速入门
2.1 内容列表
快速开始一个
MyBatis基本
CURD的操作MyBatis内部对象分析
使用DaoImpl.xml
2. 2 入门案例
2.2.1 开发准备
搭建 MyBatis 开发环境,实现第一个案例
下载 mybatis
下载后的目录结构
其中:
mybatis-3.5.1.jar ---->mybatis的核心包
lib ---->mybatis的依赖包
mybatis-3.5.1.pdf ---->mybatis使用手册
2.2 MyBatis下载
- 从github上下载,地址:github.com/mybatis/myb…
将框架以及框架的源码都下载下来,下载框架后解压,打开mybatis⽬录
○ 通过以上解压可以看到,框架⼀般都是以jar包的形式存在。我们的mybatis课程使⽤maven,所
以这个jar我们不需要。
○ 官⽅⼿册需要。
2.3 MyBatis⼊⻔程序开发步骤
● 写代码前准备:
○ 准备数据库表:汽⻋表t_car,字段包括:
id:主键(⾃增)【bigint】
car_num:汽⻋编号【
varchar】
brand:品牌【
varchar】
guide_price:⼚家指导价【
decimal类型,专⻔为财务数据准备的类型】
produce_time:⽣产时间【
char,年⽉⽇即可,10个⻓度,‘2022-10-11’】
car_type:汽⻋类型(燃油⻋、电⻋、氢能源)【
varchar】
-
使⽤navicat for mysql⼯具建表
-
使⽤navicat for mysql⼯具向t_car表中插⼊两条数据,如下:
-
创建Project:建议创建Empty Project,设置Java版本以及编译版本等。
-
步骤1:打包⽅式:jar(不需要war,因为mybatis封装的是jdbc。)
<groupId>com.powernode</groupId> <artifactId>mybatis-001-introduction</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> -
步骤2:引⼊依赖(mybatis依赖 + mysql驱动依赖)
<!--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> -
步骤3:在resources根⽬录下新建mybatis-config.xml配置⽂件(可以参考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> <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/po wernode"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <!--sql映射⽂件创建好之后,需要将该⽂件路径配置到这⾥--> <mapper resource=""/> </mappers> </configuration> -
注意1:mybatis核⼼配置⽂件的⽂件名不⼀定是mybatis-config.xml,可以是其它名字。
-
注意2:mybatis核⼼配置⽂件存放的位置也可以随意。这⾥选择放在resources根下,相当于放到了类的 根路径下。
-
步骤4:在resources根⽬录下新建CarMapper.xml配置⽂件(可以参考mybatis⼿册拷⻉)
<?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"> <!--insert sql:保存⼀个汽⻋信息--> <insert id="insertCar"> insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values (null,'102','丰⽥mirai',40.30,'2014-10-05','氢能源') </insert> </mapper> -
注意1:sql语句最后结尾可以不写“;”
-
注意2:CarMapper.xml⽂件的名字不是固定的。可以使⽤其它名字。
-
注意3:CarMapper.xml⽂件的位置也是随意的。这⾥选择放在resources根下,相当于放到了类的根路 径下。
-
注意4:将CarMapper.xml⽂件路径配置到mybatis-config.xml:
<mapper resource="CarMapper.xml"/> 1 -
步骤5:编写MyBatisIntroductionTest代码
package com.manman.test; 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; import java.io.InputStream; /** * @author Gaoziman * @version 1.0 * description: * @date 2022/9/15 22:16 */ public class MyBatisTest { @Test public void testDelete() throws IOException { // 创建SqlSessionFactoryBuilder对象 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); // 创建流 加载mybatis主配置文件 InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); // 通过流创建SqlSessionFactory SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is); // 通过SqlSessionFactory创建SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); // 使用SQL // int i = sqlSession.delete("deleteCar"); int i = sqlSession.insert("insertCar"); // 手动提交事务 sqlSession.commit(); System.out.println("受影响的行数为 : " +i); } }注意1:默认采⽤的事务管理器是:JDBC。JDBC事务默认是不提交的,需要⼿动提交。
● 步骤6:运⾏程序,查看运⾏结果,以及数据库表中的数据
2.4 关于MyBatis核⼼配置⽂件的名字和路径详解
- 核⼼配置⽂件的名字是随意的,因为以下的代码:
// ⽂件名是出现在程序中的,⽂件名如果修改了,对应这⾥的java程序也改⼀下就⾏了。
InputStream is = Thread.currentThread().getContextClassLoader().getResource
AsStream("mybatis-config.xml");
- 虽然mybatis核⼼配置⽂件的名字不是固定的,但通常该⽂件的名字叫做:mybatis-config.xml
- 虽然mybatis核⼼配置⽂件的路径不是固定的,但通常该⽂件会存放到类路径当中,这样让项⽬的移植更加健壮。
- 在mybatis中提供了⼀个类:Resources【org.apache.ibatis.io.Resources】,该类可以从类路径当中获取资源,我们通常使⽤它来获取输⼊流InputStream,代码如下
// 这种⽅式只能从类路径当中获取资源,也就是说mybatis-config.xml⽂件需要在类路径下。
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
2.5 MyBatis第⼀个⽐较完整的代码写法
package com.powernode.mybatis;
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 gaoziman
* @since 1.0
* @version 1.0
*/
public class MyBatisCompleteCodeTest {
public static void main(String[] args) {
SqlSession sqlSession = null;
try {
// 1.创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSes
sionFactoryBuilder();
// 2.创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
// 3.创建SqlSession对象
sqlSession = sqlSessionFactory.openSession();
// 4.执⾏SQL
int count = sqlSession.insert("insertCar");
System.out.println("更新了⼏条记录:" + count);
// 5.提交
sqlSession.commit();
} catch (Exception e) {
// 回滚
if (sqlSession != null) {
sqlSession.rollback();
}
e.printStackTrace();
} finally {
// 6.关闭
if (sqlSession != null) {
sqlSession.close();
}
}
}
2.6 引入单元测试
-
JUnit是专⻔做单元测试的组件。
-
在实际开发中,单元测试⼀般是由我们Java程序员来完成的。
-
我们要对我们⾃⼰写的每⼀个业务⽅法负责任,要保证每个业务⽅法在进⾏测试的时候都能通过。
-
测试的过程中涉及到两个概念:
- 期望值
- 实际值
-
期望值和实际值相同表示测试通过,期望值和实际值不同则单元测试执⾏时会报错。
-
这⾥引⼊JUnit是为了代替main⽅法。
-
-
使⽤JUnit步骤:
- 第⼀步:引⼊依赖
<!-- junit依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
-
第⼆步:编写单元测试类【测试⽤例】,测试⽤例中每⼀个测试⽅法上使⽤@Test注解进⾏标注。
-
测试⽤例的名字以及每个测试⽅法的定义都是有规范的:
测试⽤例的名字:XxxTest
测试⽅法声明格式:public void test业务⽅法名(){}
-
// 测试⽤例
public class CarMapperTest{
// 测试⽅法
@Test
public void testInsert(){}
@Test
public void testUpdate(){}
}
-
第三步:可以在类上执⾏,也可以在⽅法上执⾏
- 在类上执⾏时,该类中所有的测试⽅法都会执⾏。
- 在⽅法上执⾏时,只执⾏当前的测试⽅法。
-
编写⼀个测试⽤例,来测试insertCar业务
package com.powernode.mybatis;
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;
public class CarMapperTest {
@Test
public void testInsertCar(){
SqlSession sqlSession = null;
try {
// 1.创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSes
sionFactoryBuilder();
// 2.创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder
.build(Resources.getResourceAsStream("mybatis-config.xml"));
// 3.创建SqlSession对象
sqlSession = sqlSessionFactory.openSession();
// 4.执⾏SQL
int count = sqlSession.insert("insertCar");
System.out.println("更新了⼏条记录:" + count);
// 5.提交
sqlSession.commit();
} catch (Exception e) {
// 回滚
if (sqlSession != null) {
sqlSession.rollback();
}
e.printStackTrace();
} finally {
// 6.关闭
if (sqlSession != null) {
sqlSession.close();
}
}
}
}
执⾏单元测试,查看数据库表的变化:
2.7 引入日志框架
- 引⼊⽇志框架的⽬的是为了看清楚mybatis执⾏的具体sql。
- 启⽤标准⽇志组件,只需要在mybatis-config.xml⽂件中添加以下配置:【可参考mybatis⼿册】
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
标准⽇志也可以⽤,但是配置不够灵活,可以集成其他的⽇志组件,例如:log4j,logback等。
-
logback是⽬前⽇志框架中性能较好的,较流⾏的,所以我们选它。
-
引⼊logback的步骤:
-
第⼀步:引⼊logback相关依赖
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.11</version> <scope>test</scope> </dependency> -
第⼆步:引⼊logback相关配置⽂件(⽂件名叫做logback.xml或logback-test.xml,放到类路径
当中)
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="false"> <!--定义⽇志⽂件的存储地址--> <property name="LOG_HOME" value="/home"/> <!-- 控制台输出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncode r"> <!--格式化输出:%d表示⽇期,%thread表示线程名,%-5level:级别从左显示5 个字符宽度%msg:⽇志消息,%n是换⾏符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logge r{50} - %msg%n</pattern> </encoder> </appender> <!-- 按照每天⽣成⽇志⽂件 --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAp pender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRolling Policy"> <!--⽇志⽂件输出的⽂件名--> <FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</F ileNamePattern> <!--⽇志⽂件保留天数--> <MaxHistory>30</MaxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncode r"> <!--格式化输出:%d表示⽇期,%thread表示线程名,%-5level:级别从左显示5 个字符宽度%msg:⽇志消息,%n是换⾏符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logge r{50} - %msg%n</pattern> </encoder> <!--⽇志⽂件最⼤的⼤⼩--> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTrig geringPolicy"> <MaxFileSize>100MB</MaxFileSize> </triggeringPolicy> </appender> <!--mybatis log configure--> <logger name="com.apache.ibatis" level="TRACE"/> <logger name="java.sql.Connection" level="DEBUG"/> <logger name="java.sql.Statement" level="DEBUG"/> <logger name="java.sql.PreparedStatement" level="DEBUG"/> 再次执⾏单元测试⽅法testInsertCar,查看控制台是否有sql语句输出 每⼀次获取SqlSession对象代码太繁琐,封装⼀个⼯具类 ● ○ 2.8 MyBatis⼯具类SqlSessionUtil的封装 ● <!-- ⽇志输出级别,logback⽇志级别包括五个:TRACE < DEBUG < INFO < WARN < ER ROR --> <root level="DEBUG"> <appender-ref ref="STDOUT"/> <appender-ref ref="FILE"/> </root> </configuration>
-
-
再次执⾏单元测试⽅法testInsertCar,查看控制台是否有sql语句输出
2.8 MyBatis工具类SessionUtils的封装
大家应该可以发现,每当我们进行CEUD的时候,会发现我们每次获取SqlSession都需要很繁杂的代码,所以此时就需要一个工具类来帮我们完成,以后获取SqlSession时直接调用即可。
package com.manman.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;
/**
* @author Gaoziman
* @version 1.0
* description:
* @date 2022/9/17 16:44
*/
public class SqlSessionUtil {
private static SqlSessionFactory sqlSessionFactory;
private static SqlSession sqlSession;
/**
* 类加载时初始化sqlSessionFactory对象
*/
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 每调⽤⼀次openSession()可获取⼀个新的会话,该会话⽀持⾃动提交。
*
* @return 新的会话对象
*/
public static SqlSession getSqlSession(){
if (sqlSessionFactory != null){
sqlSession = sqlSessionFactory.openSession();
}
return sqlSession;
}
}
测试⼯具类,将testInsertCar()改造
/**
* description : 测试工具类
*/
@Test
public void testInsert() {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
int count = sqlSession.insert("insertCar");
System.out.println( "受影响的行数为 : " + count);
sqlSession.commit();
sqlSession.close();
}
第三章 使用MyBatis完成CRUD
-
准备⼯作
-
创建module(Maven的普通Java模块):mybatis-002-crud
-
pom.xml
-
打包⽅式jar
-
依赖:
- mybatis依赖
- mysql驱动依赖
- junit依赖
- logback依赖
-
-
mybatis-config.xml放在类的根路径下
-
CarMapper.xml放在类的根路径下
-
logback.xml放在类的根路径下
-
提供com.powernode.mybatis.utils.SqlSessionUtil⼯具类
-
创建测试⽤例:com.powernode.mybatis.CarMapperTest
-
3.1 Inser(Create)
分析以下SQL映射⽂件中SQL语句存在的问题
<?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">
<insert id="insertCar">
insert into t_car(car_num,brand,guide_price,produce_time,car_typ
e) values('103', '奔驰E300L', 50.3, '2022-01-01', '燃油⻋')
</insert>
</mapper>
存在的问题是:SQL语句中的值不应该写死,值应该是⽤户提供的。之前的JDBC代码是这样写的:
// JDBC中使⽤ ? 作为占位符。那么MyBatis中会使⽤什么作为占位符呢?
String sql = "insert into t_car(car_num,brand,guide_price,produce_time,car_
type) values(?,?,?,?,?)";
// ......
// 给 ? 传值。那么MyBatis中应该怎么传值呢?
ps.setString(1,"103");
ps.setString(2,"奔驰E300L");
ps.setDouble(3,50.3);
ps.setString(4,"2022-01-01");
ps.setString(5,"燃油⻋");
在MyBatis中可以这样做:
在Java程序中,将数据放到Map集合中
在sql语句中使⽤ #{map集合的key} 来完成传值,#{} 等同于JDBC中的 ? ,#{}就是占位符
Java程序这样写:
package com.powernode.mybatis;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
/**
* 测试MyBatis的CRUD
* @author manman
* @version 1.0
* @since 1.0
*/
public class CarMapperTest {
@Test
public void testInsertCar(){
// 准备数据
Map<String, Object> map = new HashMap<>();
map.put("carNum", "103");
map.put("brand", "奔驰E300L");
map.put("guidePrice", 50.3);
map.put("produceTime", "2020-10-01");
map.put("carType", "燃油⻋");
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执⾏SQL语句(使⽤map集合给sql语句传递数据)
int count = sqlSession.insert("insertCar", map);
System.out.println("插⼊了⼏条记录:" + count);
} }
SQL语句是这样写的 :
<?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">
<insert id="insertCar">
insert into t_car(car_num,brand,guide_price,produce_time,car_typ
e) values(#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
</mapper>
注意 : 我们在给key值起名的时候,一定要见名知意,不要使用k1.k2.k3.k4…类似这样的名字
#{} 的⾥⾯必须填写map集合的key,不能随便写。运⾏测试程序,查看数据库:
如果#{}⾥写的是map集合中不存在的key会有什么问题?
<?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">
<insert id="insertCar">
insert into t_car(car_num,brand,guide_price,produce_time,car_typ
e) values(#{carNum1},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
</mapper>
运行程序 :
通过测试,看到程序并没有报错。正常执⾏。不过 #{kk} 的写法导致⽆法获取到map集合中的数据,最终
导致数据库表car_num插⼊了NULL。
使⽤Map集合可以传参,那使⽤ pojo(简单普通的java对象)可以完成传参吗?测试⼀下:
● 第⼀步:定义⼀个pojo类Car,提供相关属性。
package com.manman.pojo;
/**
* @author Gaoziman
* @version 1.0
* description:
* @date 2022/9/18 0:54
*/
public class Car {
private Long id;
private String carNum;
private String brand;
private Double guidePrice;
private String produceTime;
private String carType;
public Car() {
}
@Override
public String toString() {
return "Car{" +
"id=" + id +
", carNum='" + carNum + '\'' +
", brand='" + brand + '\'' +
", guidePrice=" + guidePrice +
", produceTime='" + produceTime + '\'' +
", carType='" + carType + '\'' +
'}';
}
public Car(Long id, String carNum, String brand, Double guidePrice, String produceTime, String carType) {
this.id = id;
this.carNum = carNum;
this.brand = brand;
this.guidePrice = guidePrice;
this.produceTime = produceTime;
this.carType = carType;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCarNum() {
return carNum;
}
public void setCarNum(String carNum) {
this.carNum = carNum;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Double getGuidePrice() {
return guidePrice;
}
public void setGuidePrice(Double guidePrice) {
this.guidePrice = guidePrice;
}
public String getProduceTime() {
return produceTime;
}
public void setProduceTime(String produceTime) {
this.produceTime = produceTime;
}
public String getCarType() {
return carType;
}
public void setCarType(String carType) {
this.carType = carType;
}
}
-
第二步 :Java小程序
@Test public void testInsertCarByPOJO(){ // 创建POJO,封装数据 Car car = new Car(); car.setCarNum("103"); car.setBrand("奔驰C200"); car.setGuidePrice(33.23); car.setProduceTime("2020-10-11"); car.setCarType("燃油⻋"); // 获取SqlSession对象 SqlSession sqlSession = SqlSessionUtil.openSession(); // 执⾏SQL,传数据 int count = sqlSession.insert("insertCarByPOJO", car); System.out.println("插⼊了⼏条记录" + count); } -
第三步 :SQL语句
<insert id="insertCarByPOJO"> <!--#{} ⾥写的是POJO的属性名--> insert into t_car(car_num,brand,guide_price,produce_time,car_type) values (#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType}) </insert> -
运行程序 ,查看数据库表 :
#{} ⾥写的是POJO的属性名,如果写成其他的会有问题吗?
<insert id="insertCarByPOJO">
insert into t_car(car_num,brand,guide_price,produce_time,car_type) values
(#{a},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
错误信息中描述:在Car类中没有找到a属性的getter⽅法。
修改POJO类Car的代码,只将getCarNum()⽅法名修改为getA(),其他代码不变,如下:
-
再运行程序,查看数据表 :
经过测试得出结论:
如果采⽤map集合传参,#{} ⾥写的是map集合的key,如果key不存在不会报错,数据库表中会插 ⼊NULL。
如果采⽤POJO传参,#{} ⾥写的是get⽅法的⽅法名去掉get之后将剩下的单词⾸字⺟变⼩写(例 如:getAge对应的是#{age},getUserName对应的是#{userName}),如果这样的get⽅法不存在会报错。
注意:其实传参数的时候有⼀个属性parameterType,这个属性⽤来指定传参的数据类型,不过这个属
性是可以省略的
<insert id="insertCar" parameterType="java.util.Map">
insert into t_car(car_num,brand,guide_price,produce_time,car_type) values
(#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert> <insert id="insertCarByPOJO" parameterType="com.manman.pojo.Car"
>
insert into t_car(car_num,brand,guide_price,produce_time,car_type) values
(#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
3.2 delete(Delete)
需求:根据car_num进⾏删除。
SQL语句这样写:
<delete id="deleteByCarNum">
delete from t_car where car_num = #{SuiBianXie}
</delete>
Java程序实在这样写的 :
@Test
public void testDeleteByCarNum(){
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执⾏SQL语句
int count = sqlSession.delete("deleteByCarNum", "102");
System.out.println("删除了⼏条记录:" + count); }
注意:当占位符只有⼀个的时候,${} ⾥⾯的内容可以随便写。
3.3 update(Update)
需求:修改id=34的Car信息,car_num为102,brand为⽐亚迪汉,guide_price为30.23,produce_time
为2018-09-10,car_type为电⻋
修改前:
SQL语句如下 :
<update id="updateCarByPOJO">
update t_car set
car_num = #{carNum}, brand = #{brand},
guide_price = #{guidePrice}, produce_time = #{produceTime},
car_type = #{carType}
where id = #{id}
</update>
Java代码如下 :
@Test
public void testUpdateCarByPOJO(){
// 准备数据
Car car = new Car();
car.setId(34L);
car.setCarNum("102");
car.setBrand("⽐亚迪汉");
car.setGuidePrice(30.23);
car.setProduceTime("2018-09-10");
car.setCarType("电⻋");
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执⾏SQL语句
int count = sqlSession.update("updateCarByPOJO", car);
System.out.println("更新了⼏条记录:" + count);
}
当然了,如果使⽤map传数据也是可以的。
3.4 select(Retrieve)
select语句和其它语句不同的是:查询会有⼀个结果集。来看mybatis是怎么处理结果集的!!!
查询一条语句
需求:查询id为1的Car信息
SQL语句如下:
<select id="selectCarById">
select * from t_car where id = #{id}
</select>
Java程序如下 :
@Test
public void testSelectCarById(){
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执⾏SQL语句
Object car = sqlSession.selectOne("selectCarById", 1);
System.out.println(car); }
运行结果如下 :
### Error querying database. Cause: org.apache.ibatis.executor.ExecutorExc
eption:
A query was run and no Result Maps were found for the Mapped Statement
'car.selectCarById'. 【翻译】:对于⼀个查询语句来说,没有找到查询的结果映射。
It's likely that neither a Result Type nor a Result Map was specified. 【翻译】:很可能既没有指定结果类型,也没有指定结果映射。
以上的异常⼤致的意思是:对于⼀个查询语句来说,你需要指定它的“结果类型”或者“结果映射”。
所以说,你想让mybatis查询之后返回⼀个Java对象的话,⾄少你要告诉mybatis返回⼀个什么类型的
Java对象,可以在标签中添加resultType属性,⽤来指定查询要转换的类型:
<select id="selectCarById" resultType="com.manman.pojo.Car">
select * from t_car where id = #{id}
</select>
运⾏后之前的异常不再出现了,这说明添加了resultType属性之后,解决了之前的异常,可以看出
resultType是不能省略的。
仔细观察控制台的⽇志信息,不难看出,结果查询出了⼀条。并且每个字段都查询到值了:Row: 1, 100, 宝⻢520Li, 41.00, 2022-09-01, 燃油⻋
但是奇怪的是返回的Car对象,只有id和brand两个属性有值,其它属性的值都是null,这是为什么呢?我 们来观察⼀下查询结果列名和Car类的属性名是否能⼀⼀对应:
查询结果集的列名:id, car_num, brand, guide_price, produce_time, car_type
Car类的属性名:id, carNum, brand, guidePrice, produceTime, carType
通过观察发现:只有id和brand是⼀致的,其他字段名和属性名对应不上,这是不是导致null的原因呢?
我们尝试在sql语句中使⽤as关键字来给查询结果列名起别名试试 :
<select id="selectCarById" resultType="com.powernode.mybatis.pojo.Car">
select
id, car_num as carNum, brand, guide_price as guidePrice, produce_time a
s produceTime, car_type as carType
from
t_car
where
id = #{id}
</select>
运行结果如下 :
通过测试得知,如果当查询结果的字段名和java类的属性名对应不上的话,可以采⽤as关键字起别名,
当然还有其它解决⽅案,我们后⾯再看。
查询所有数据
需求:查询所有的Car信息。
SQL语句如下:
<!--虽然结果是List集合,但是resultType属性需要指定的是List集合中元素的类型。-->
<select id="selectCarAll" resultType="com.powernode.mybatis.pojo.Car">
<!--记得使⽤as起别名,让查询结果的字段名和java类的属性名对应上。-->
select
id, car_num as carNum, brand, guide_price as guidePrice, produce_time a
s produceTime, car_type as carType
from
t_car
</select>
Java代码如下 :
@Test
public void testSelectCarAll(){
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执⾏SQL语句
List<Object> cars = sqlSession.selectList("selectCarAll");
// 输出结果
cars.forEach(car -> System.out.println(car));
}
3.5 关于SQL中的namespace
在SQL Mapper配置⽂件中标签的namespace属性可以翻译为命名空间,这个命名空间主要是
为了防⽌sqlId冲突的。
创建CarMapper2.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="car2">
<select id="selectCarAll" resultType="com.powernode.manman.pojo.Car">
select
id, car_num as carNum, brand, guide_price as guidePrice, produ
ce_time as produceTime, car_type as carType
from
t_car
</select>
</mapper>
不难看出,CarMapper.xml和CarMapper2.xml⽂件中都有 id=“selectCarAll”
将CarMapper2.xml配置到mybatis-config.xml⽂件中。
<mappers>
<mapper resource="CarMapper.xml"/>
<mapper resource="CarMapper2.xml"/>
</mappers>
编写Java代码如下 :
@Test
public void testNamespace(){
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执⾏SQL语句
List<Object> cars = sqlSession.selectList("selectCarAll");
// 输出结果
cars.forEach(car -> System.out.println(car)); }
运行结果如下:
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: java.lang.IllegalArgumentException:
selectCarAll is ambiguous in Mapped Statements collection (try using the
full name including the namespace, or rename one of the entries)
【翻译】selectCarAll在Mapped Statements集合中不明确(请尝试使⽤包含名称空间的全 名,或重命名其中⼀个条⽬)
【⼤致意思是】selectCarAll重名了,你要么在selectCarAll前添加⼀个名称空间,要有你改 个其它名字。
Java代码修改如下 :
@Test
public void testNamespace(){
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执⾏SQL语句
//List<Object> cars = sqlSession.selectList("car.selectCarAll");
List<Object> cars = sqlSession.selectList("car2.selectCarAll");
// 输出结果
cars.forEach(car -> System.out.println(car)); }
运行结果如下 :
第四章 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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="CarMapper.xml"/>
<mapper resource="CarMapper2.xml"/>
</mappers>
</configuration>
-
configuration:根标签,表示配置信息。
-
environments:环境(多个),以“s”结尾表示复数,也就是说mybatis的环境可以配置多个数据源。
-
default属性:表示默认使⽤的是哪个环境,default后⾯填写的是environment的id。default的 值只需要和environment的id值⼀致即可。
-
environment:具体的环境配置(主要包括:事务管理器的配置 + 数据源的配置)
- id:给当前环境⼀个唯⼀标识,该标识⽤在environments的default后⾯,⽤来指定默认环境的 选择。
-
transactionManager:配置事务管理器
-
type属性:指定事务管理器具体使⽤什么⽅式,可选值包括两个
■ JDBC:使⽤JDBC原⽣的事务管理机制。底层原理:事务开启
conn.setAutoCommit(false); …处理业务…事务提交conn.commit();
■ MANAGED:交给其它容器来管理事务,⽐如WebLogic、JBOSS等。如果没有管理事务的 容器,则没有事务。没有事务的含义:只要执⾏⼀条DML语句,则提交⼀次。
-
dataSource:指定数据源
-
type属性:⽤来指定具体使⽤的数据库连接池的策略,可选值包括三个
- UNPOOLED:采⽤传统的获取连接的⽅式,虽然也实现Javax.sql.DataSource接⼝,但是并没有使⽤池的思想。
-
property可以是:
○ driver 这是 JDBC 驱动的 Java 类全限定名。
○ url 这是数据库的 JDBC URL 地址。
○ username 登录数据库的⽤户名。
○ password 登录数据库的密码。
○ defaultTransactionIsolationLevel 默认的连接事务隔离级别。
○ defaultNetworkTimeout 等待数据库操作完成的默认⽹络超时时间(单位:毫秒)
-
■ POOLED:采⽤传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实
现。
-
property可以是(除了包含UNPOOLED中之外):
- poolMaximumActiveConnections 在任意时间可存在的活动(正在使⽤)连接数量,默认值:10
- poolMaximumIdleConnections 任意时间可能存在的空闲连接数。
- 其它…
■ JNDI:采⽤服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到
DataSource是不⼀样。如果不是web或者maven的war⼯程,JNDI是不能使⽤的。
-
property可以是(最多只包含以下两个属性):
- initial_context 这个属性⽤来在 InitialContext 中寻找上下⽂(即,
- initialContext.lookup(initial_context))这是个可选属性,如果忽略,那么将会直接从
- InitialContext 中寻找 data_source 属性。
○ data_source 这是引⽤数据源实例位置的上下⽂路径。提供了 initial_context 配置时会 在其返回的上下⽂中进⾏查找,没有提供时则直接在 InitialContext 中查找。
- mappers:在mappers标签中可以配置多个sql映射⽂件的路径。
- mapper:配置某个sql映射⽂件的路径
- resource属性:使⽤相对于类路径的资源引⽤⽅式
- url属性:使⽤完全限定资源定位符(URL)⽅式
4.1 environment
mybatis-003-configuration
<?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="dev">-->
<!--默认使⽤⽣产环境-->
<environments default="production">
<!--开发环境-->
<environment id="dev">
<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="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
<!--⽣产环境-->
<environment id="production">
<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>
<?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="car">
<insert id="insertCar">
insert into t_car(id,car_num,brand,guide_price,produce_time,car_ty
pe) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carTyp
e})
</insert>
</mapper>
package com.powernode.mybatis;
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;
public class ConfigurationTest {
@Test
public void testEnvironment() throws Exception{
// 准备数据
Car car = new Car();
car.setCarNum("133");
car.setBrand("丰⽥霸道");
car.setGuidePrice(50.3);
car.setProduceTime("2020-01-10");
car.setCarType("燃油⻋");
// ⼀个数据库对应⼀个SqlSessionFactory对象
// 两个数据库对应两个SqlSessionFactory对象,以此类推
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSession
FactoryBuilder();
// 使⽤默认数据库
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.bui
ld(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession(true);
int count = sqlSession.insert("insertCar", car);
System.out.println("插⼊了⼏条记录:" + count);
// 使⽤指定数据库
SqlSessionFactory sqlSessionFactory1 = sqlSessionFactoryBuilder.bu
ild(Resources.getResourceAsStream("mybatis-config.xml"), "dev");
SqlSession sqlSession1 = sqlSessionFactory1.openSession(true);
int count1 = sqlSession1.insert("insertCar", car);
System.out.println("插⼊了⼏条记录:" + count1);
}
}
执行结果 :
4.2 transactionManager
<?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="dev">
<environment id="dev">
<transactionManager type="MANAGED"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/po
wernode"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="CarMapper.xml"/>
</mappers>
</configuration>
@Test
public void testTransactionManager() throws Exception{
// 准备数据
Car car = new Car();
car.setCarNum("133");
car.setBrand("丰⽥霸道");
car.setGuidePrice(50.3);
car.setProduceTime("2020-01-10");
car.setCarType("燃油⻋");
// 获取SqlSessionFactory对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFact
oryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(R
esources.getResourceAsStream("mybatis-config2.xml"));
// 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 执⾏SQL
int count = sqlSession.insert("insertCar", car);
System.out.println("插⼊了⼏条记录:" + count); }
-
当事务管理器是:JDBC
- 采⽤JDBC的原⽣事务机制:
-
开启事务:conn.setAutoCommit(false);
-
处理业务…
-
提交事务:conn.commit();
-
当事务管理器是:MANAGED
- 交给容器去管理事务,但⽬前使⽤的是本地程序,没有容器的⽀持,当mybatis找不到容器的⽀持 时:没有事务。也就是说只要执⾏⼀条DML语句,则提交⼀次。
4.3 dataSource
<?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="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/po
wernode"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="CarMapper.xml"/>
</mappers>
</configuration>
@Test
public void testDataSource() throws Exception{
// 准备数据
Car car = new Car();
car.setCarNum("133");
car.setBrand("丰⽥霸道");
car.setGuidePrice(50.3);
car.setProduceTime("2020-01-10");
car.setCarType("燃油⻋");
// 获取SqlSessionFactory对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFact
oryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(R
esources.getResourceAsStream("mybatis-config3.xml"));
// 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
// 执⾏SQL
int count = sqlSession.insert("insertCar", car);
System.out.println("插⼊了⼏条记录:" + count);
// 关闭会话
sqlSession.close(); }
当type是UNPOOLED,控制台输出:
修改配置⽂件mybatis-config3.xml中的配置:
<dataSource type="POOLED">
Java测试程序不需要修改,直接执⾏,看控制台输出:
通过测试得出:UNPOOLED不会使⽤连接池,每⼀次都会新建JDBC连接对象。POOLED会使⽤数据库
连接池。【这个连接池是mybatis⾃⼰实现的。】
<dataSource type="JNDI">
JNDI的⽅式:表示对接JNDI服务器中的连接池。这种⽅式给了我们可以使⽤第三⽅连接池的接⼝。如果
想使⽤dbcp、c3p0、druid(德鲁伊)等,需要使⽤这种⽅式。
这种再重点说⼀下type="POOLED"的时候,它的属性有哪些?
<?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="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/po
wernode"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<!--最⼤连接数-->
<property name="poolMaximumActiveConnections" value="3"/>
<!--最多空闲数量-->
<property name="poolMaximumIdleConnections" value="1"/>
<!--强⾏回归池的时间-->
<property name="poolMaximumCheckoutTime" value="20000"/>
<!--这是⼀个底层设置,如果获取连接花费了相当⻓的时间,连接池会打印状 态⽇志并重新尝试获取⼀个连接(避免在误配置的情况下⼀直失败且不打印⽇志),默认值:20000
毫秒(即 20 秒)。-->
<property name="poolTimeToWait" value="20000"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="CarMapper.xml"/>
</mappers>
</configuration>
poolMaximumActiveConnections:最⼤的活动的连接数量。默认值10
poolMaximumIdleConnections:最⼤的空闲连接数量。默认值5
poolMaximumCheckoutTime:强⾏回归池的时间。默认值20秒。
poolTimeToWait:当⽆法获取到空闲连接时,每隔20秒打印⼀次⽇志,避免因代码配置有误,导致傻
等。(时⻓是可以配置的)
当然,还有其他属性。对于连接池来说,以上⼏个属性⽐较重要。
最⼤的活动的连接数量就是连接池连接数量的上限。默认值10,如果有10个请求正在使⽤这10个连接,
第11个请求只能等待空闲连接。
最⼤的空闲连接数量。默认值5,如何已经有了5个空闲连接,当第6个连接要空闲下来的时候,连接池会
选择关闭该连接对象。来减少数据库的开销。
需要根据系统的并发情况,来合理调整连接池最⼤连接数以及最多空闲数量。充分发挥数据库连接池的
性能。【可以根据实际情况进⾏测试,然后调整⼀个合理的数量。】
下图是默认配置:
在以上配置的基础之上,可以编写java程序测试:
@Test
public void testPool() throws Exception{
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config3.xml"));
for (int i = 0; i < 4; i++) {
SqlSession sqlSession = sqlSessionFactory.openSession();
Object selectCarByCarNum = sqlSession.selectOne("selectCarByCarNum"
);
}
}
<select id="selectCarByCarNum" resultType="com.powernode.mybatis.pojo.Car">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car where car_num = '100'
</select>
4.4 Properties
mybatis提供了更加灵活的配置,连接数据库的信息可以单独写到⼀个属性资源⽂件中,假设在类的根路
径下创建jdbc.properties⽂件,配置如下:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/powernode
在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>
<!--引⼊外部属性资源⽂件-->
<properties resource="jdbc.properties">
<property name="jdbc.username" value="root"/>
<property name="jdbc.password" value="root"/>
</properties>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--${key}使⽤-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="CarMapper.xml"/>
</mappers>
</configuration>
编写Java程序测试:
@Test
public void testProperties() throws Exception{
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFacto
ryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Re
sources.getResourceAsStream("mybatis-config4.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
Object car = sqlSession.selectOne("selectCarByCarNum");
System.out.println(car); }
properties两个属性:
resource:这个属性从类的根路径下开始加载。【常⽤的。】
url:从指定的url加载,假设⽂件放在d:/jdbc.properties,这个url可以写成:
file:///d:/jdbc.properties。注意是三个斜杠哦。
注意:如果不知道mybatis-config.xml⽂件中标签的编写顺序的话,可以有两种⽅式知道它的顺序:
第⼀种⽅式:查看dtd约束⽂件。
第⼆种⽅式:通过idea的报错提示信息。【⼀般采⽤这种⽅式】
4.5 mapper
mapper标签⽤来指定SQL映射⽂件的路径,包含多种指定⽅式,这⾥先主要看其中两种:
第⼀种:resource,从类的根路径下开始加载【⽐url常⽤】
<mappers>
<mapper resource="CarMapper.xml"/>
</mappers>
如果是这样写的话,必须保证类的根下有CarMapper.xml⽂件。
如果类的根路径下有⼀个包叫做test,CarMapper.xml如果放在test包下的话,这个配置应该是这样写:
<mappers>
<mapper resource="test/CarMapper.xml"/>
</mappers>
第⼆种:url,从指定的url位置加载
假设CarMapper.xml⽂件放在d盘的根下,这个配置就需要这样写:
<mappers>
<mapper url="file:///d:/CarMapper.xml"/>
</mappers>
第五章 在WEB中应⽤MyBatis(使⽤MVC架构模式)
⽬标:
● 掌握mybatis在web应⽤中怎么⽤
● mybatis三⼤对象的作⽤域和⽣命周期
● ThreadLocal原理及使⽤
● 巩固MVC架构模式
● 为学习MyBatis的接⼝代理机制做准备
实现功能:
● 银⾏账户转账
使⽤技术:
● HTML + Servlet + MyBatis
WEB应⽤的名称:
● bank
5.1 需求分析
5.2 数据库表的设计和准备数据
5.3 实现步骤
第⼀步:环境搭建
● IDEA中创建Maven WEB应⽤(mybatis-04-web)
-
IDEA配置Tomcat,这⾥Tomcat使8+版本。并部署应⽤到tomcat
-
默认创建的maven web应⽤没有java和resources⽬录,包括两种解决⽅案
○ 第⼀种:⾃⼰⼿动加上。
○ 第⼆种:修改maven-archetype-webapp-1.4.jar中的配置⽂件
● 引⼊相关依赖
○ 编译器版本修改为17
○ 引⼊的依赖包括:mybatis,mysql驱动,junit,logback,servlet。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w
3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://mave
n.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.powernode</groupId>
<artifactId>mybatis-004-web</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>mybatis-004-web</name>
<url>http://localhost:8080/bank</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<!--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>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!--logback依赖-->
<dependency>
<groupId>ch.qos.logback</groupId> 123456789
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
<!--servlet依赖-->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>mybatis-004-web</finalName>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
-
引⼊相关配置⽂件,放到resources⽬录下(全部放到类的根路径下)
- mybatis-config.xml
- AccountMapper.xml
- logback.xml
- jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=12123
<?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 resource="jdbc.properties"/>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--⼀定要注意这⾥的路径哦!!!-->
<mapper resource="AccountMapper.xml"/>
</mappers>
</configuration>
<?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">
</mapper>
第⼆步:前端⻚⾯index.html
<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<title>银⾏账户转账</title>
</head> <body>
<!--/bank是应⽤的根,部署web应⽤到tomcat的时候⼀定要注意这个名字-->
<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>
第三步:创建pojo包、service包、dao包、web包、utils包
- com.manman.pojo
- com.manman.sevice
- com.manman.dao
- com.manman.web
- com.manman.utils
第四步:定义pojo类:Account
public class Account {
private Long id;
private String actno;
private Double balance;
}
第五步:编写AccountDao接⼝,以及AccountDaoImpl实现类
-
分析dao中⾄少要提供⼏个⽅法,才能完成转账:
转账前需要查询余额是否充⾜:selectByActno
转账时要更新账户:update
public interface AccountDao {
/**
* 根据账号获取账户信息
* @param actno 账号
* @return 账户信息
*/
Account selectByActno(String actno);
/**
* 更新账户信息
* @param act 账户信息
* @return 1表示更新成功,其他值表示失败
*/
int update(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 {
@Override
public Account selectByActno(String actno) {
SqlSession sqlSession = SqlSessionUtil.openSession();
Account act = (Account)sqlSession.selectOne("selectByActno", actno
);
sqlSession.close();
return act;
}
@Override
public int update(Account act) {
SqlSession sqlSession = SqlSessionUtil.openSession();
int count = sqlSession.update("update", act);
sqlSession.commit();
sqlSession.close();
return count;
} }
第六步:AccountDaoImpl中编写了mybatis代码,需要编写SQL映射⽂件了
<?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.powernode.bank.pojo.Accoun
t">
select * from t_act where actno = #{actno}
</select>
<update id="update">
update t_act set balance = #{balance} where actno = #{actno}
</update>
</mapper>
第七步:编写AccountService接⼝以及AccountServiceImpl
public class MoneyNotEnoughException extends Exception{
public MoneyNotEnoughException(){
}
public MoneyNotEnoughException(String msg){ super(msg);
}
}
public class AppException extends Exception{
public AppException(){}
public AppException(String msg){ super(msg);
}
}
package com.powernode.bank.service;
import com.powernode.bank.exception.AppException;
import com.powernode.bank.exception.MoneyNotEnoughException;
/**
* 账户业务类。
* @author
* @version 1.0
* @since 1.0
*/
public interface AccountService {
/**
* 银⾏账户转正
* @param fromActno 转出账户
* @param toActno 转⼊账户
* @param money 转账⾦额
* @throws MoneyNotEnoughException 余额不⾜异常
* @throws AppException App发⽣异常
*/
void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException;
}
package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exception.AppException;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String fromActno, String toActno, double money) t
hrows MoneyNotEnoughException, AppException {
// 查询转出账户的余额
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money) {
throw new MoneyNotEnoughException("对不起,您的余额不⾜。");
}
try {
// 程序如果执⾏到这⾥说明余额充⾜
// 修改账户余额
Account toAct = accountDao.selectByActno(toActno);
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
// 更新数据库
accountDao.update(fromAct);
accountDao.update(toAct);
} catch (Exception e) {
throw new AppException("转账失败,未知原因!");
}
}
}
第⼋步:编写AccountController
@WebServlet("/transfer")
public class AccountController extends HttpServlet {
private AccountService accountService = new AccountServiceImpl();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, IOException {
// 获取响应流
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
// 获取账户信息
String fromActno = request.getParameter("fromActno");
String toActno = request.getParameter("toActno");
double money = Integer.parseInt(request.getParameter("money"));
// 调⽤业务⽅法完成转账
try {
accountService.transfer(fromActno, toActno, money);
out.print("<h1>转账成功!!!</h1>");
} catch (MoneyNotEnoughException e) {
out.print(e.getMessage());
} catch (AppException e) {
out.print(e.getMessage());
}
}
}
启动服务器,打开浏览器,输⼊地址:http://localhost:8080/bank,测试:
5.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 session = sqlSessionFactory.openSession()) {
// 你的应⽤逻辑代码 }
事务问题
在之前的转账业务中,更新了两个账户,我们需要保证它们的同时成功或同时失败,这个时候就需要使⽤事务机
制,在transfer⽅法开始执⾏时开启事务,直到两个更新都成功之后,再提交事务,我们尝试将transfer⽅法进
⾏如下修改:
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String fromActno, String toActno, double money) t
hrows MoneyNotEnoughException, AppException {
// 查询转出账户的余额
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money) {
throw new MoneyNotEnoughException("对不起,您的余额不⾜。");
}
try {
// 程序如果执⾏到这⾥说明余额充⾜
// 修改账户余额
Account toAct = accountDao.selectByActno(toActno);
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
// 更新数据库(添加事务)
SqlSession sqlSession = SqlSessionUtil.openSession();
accountDao.update(fromAct);
// 模拟异常
String s = null;
s.toString();
accountDao.update(toAct);
sqlSession.commit();
sqlSession.close();
} catch (Exception e) {
throw new AppException("转账失败,未知原因!");
}
}
}
运⾏前注意看数据库表中当前的数据:
执行程序 :
再次查看数据库表中的数据:
傻眼了吧!!!事务出问题了,转账失败了,钱仍然是少了1万。这是什么原因呢?主要是因为service 和dao中使⽤的SqlSession对象不是同⼀个。
怎么办?为了保证service和dao中使⽤的SqlSession对象是同⼀个,可以将SqlSession对象存放到
ThreadLocal当中。修改SqlSessionUtil⼯具类:
package com.manman.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;
/**
* @author Gaoziman
* @version 1.0
* description:
* @date 2022/9/17 16:44
*/
public class SqlSessionUtil {
private SqlSessionUtil(){}
private static SqlSessionFactory 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<>();
/**
* 获取会话对象。
* @return 会话对象
*/
public static SqlSession getSqlSession(){
SqlSession sqlSession = local.get();
if (sqlSession == null) {
sqlSession = sqlSessionFactory.openSession();
// 将sqlSession对象绑定到当前线程上。
local.set(sqlSession);
}
return sqlSession;
}
/**
* 关闭SqlSession对象(从当前线程中移除SqlSession对象。)
* @param sqlSession
*/
public static void close(SqlSession sqlSession){
if (sqlSession != null) {
sqlSession.close();
// 注意移除SqlSession对象和当前线程的绑定关系。
// 因为Tomcat服务器支持线程池。也就是说:用过的线程对象t1,可能下一次还会使用这个t1线程。
local.remove();
}
}
}
修改dao中的⽅法:AccountDaoImpl中所有⽅法中的提交commit和关闭close代码全部删除。
当前数据库表中的数据:
再次运⾏程序:
查看数据库表:没有问题
再测试转账成功:
如果余额不⾜呢:
账户的余额依然正常:
第六章 MyBatis小技巧
6.1 #{}和${}
#{}:先编译sql语句,再给占位符传值,底层是PreparedStatement实现。可以防⽌sql注⼊,⽐较常 ⽤。
${}:先进⾏sql语句拼接,然后再编译sql语句,底层是Statement实现。存在sql注⼊现象。只有在需要 进⾏sql语句关键字拼接的情况下才会⽤到。
需求:根据car_type查询汽⻋
模块名:mybatis-005-antic
使用#{}
<select id="selectAllById" resultType="com.manman.pojo.Car">
select
id, car_num carNum ,brand,guide_price guidePrice ,produce_time produceTime,car_type carType
from t_car
where id =#{id}
</select>
使用${}
<select id="selectFuzzy" resultType="com.manman.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>
CarMapper.xml,放在类的根路径下:注意namespace必须和接⼝名⼀致。id必须和接⼝中⽅法名⼀致。
6.2 什么情况下必须使⽤${}
当需要进⾏sql语句关键字拼接的时候。必须使⽤${}
需求:通过向sql语句中注⼊asc或desc关键字,来完成数据的升序或降序排列。
先使⽤#{}尝试:
CarMapper接⼝:
/**
* 查询所有的Car
* @param ascOrDesc asc或desc
* @return
*/
List<Car> selectAll(String ascOrDesc);
CarMapper.xml⽂件:
<select id="selectAll" resultType="com.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as prod
uceTime,car_type as carType
from
t_car
order by carNum #{key}
</select>
测试程序
@Test
public void testSelectAll(){
CarMapper mapper = (CarMapper) SqlSessionUtil.openSession().getMapper(C
arMapper.class);
List<Car> cars = mapper.selectAll("desc");
cars.forEach(car -> System.out.println(car)); }
运行结果 :
报错的原因是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 carNum ‘desc’
desc是⼀个关键字,不能带单引号的,所以在进⾏sql语句关键字拼接的时候,必须使⽤${}
6.3 拼接表名
业务背景:实际开发中,有的表数据量⾮常庞⼤,可能会采⽤分表⽅式进⾏存储,⽐如每天⽣成⼀张表,表的名字与⽇期挂钩,例如: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
<select id="selectAllByTableName" resultType="car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as prod
uceTime,car_type as carType
from
${tableName}
</select>
/**
* 根据表名查询所有的Car
* @param tableName
* @return
*/
List<Car> selectAllByTableName(String tableName);
@Test
public void testSelectAllByTableName(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
List<Car> cars = mapper.selectAllByTableName("t_car");
cars.forEach(car -> System.out.println(car)); }
执行结果:
6.4 批量删除
业务背景:⼀次删除多条记录
对应的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)
/**
* 根据id批量删除
* @param ids
* @return
*/
int deleteBatch(String ids);
<delete id="deleteBatch">
delete from t_car where id in(${ids})
</delete>
@Test
public void testDeleteBatch(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
int count = mapper.deleteBatch("1,2,3");
System.out.println("删除了⼏条记录:" + count);
SqlSessionUtil.openSession().commit();
}
执行结果 :
6.5 模糊查询
需求:查询奔驰系列的汽⻋。【只要品牌brand中含有奔驰两个字的都查询出来。】
使⽤${}
/**
* 根据品牌进⾏模糊查询
* @param likeBrank
* @return
*/
List<Car> selectLikeByBrand(String likeBrank);
<select id="selectLikeByBrand" resultType="Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as prod
uceTime,car_type as carType
from
t_car
where
brand like '%${brand}%'
</select>
@Test
public void testSelectLikeByBrand(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
List<Car> cars = mapper.selectLikeByBrand("奔驰");
cars.forEach(car -> System.out.println(car)); }
执行结果 :
使用#{}
第⼀种:concat函数
<select id="selectLikeByBrand" resultType="Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as prod
uceTime,car_type as carType
from
t_car
where
brand like concat('%',#{brand},'%')
</select>
执行结果 :
第⼆种:双引号⽅式
<select id="selectLikeByBrand" resultType="Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as prod
uceTime,car_type as carType
from
t_car
where
brand like "%"#{brand}"%"
</select>
6.6 插⼊数据时获取⾃动⽣成的主键
前提是:主键是⾃动⽣成的。
业务背景:⼀个⽤户有多个⻆⾊。
插⼊⼀条新的记录之后,⾃动⽣成了主键,⽽这个主键需要在其他表中使⽤时。
插⼊⼀个⽤户数据的同时需要给该⽤户分配⻆⾊:需要将⽣成的⽤户的id插⼊到⻆⾊表的user_id字段 上。
第⼀种⽅式:可以先插⼊⽤户数据,再写⼀条查询语句获取id,然后再插⼊user_id字段。【⽐较麻烦】
第⼆种⽅式:mybatis提供了⼀种⽅式更加便捷。
/**
* 获取⾃动⽣成的主键
* @param car
*/
void insertUseGeneratedKeys(Car car);
<insert id="insertUseGeneratedKeys" useGeneratedKeys="true" keyProperty="i
d">
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) val
ues(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
@Test
public void testInsertUseGeneratedKeys(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cl
ass);
Car car = new Car();
car.setCarNum("5262");
car.setBrand("BYD汉");
car.setGuidePrice(30.3);
car.setProduceTime("2020-10-11");
car.setCarType("新能源");
mapper.insertUseGeneratedKeys(car);
SqlSessionUtil.openSession().commit();
System.out.println(car.getId()); }
第七章 MyBatis查询语句
7.1 返回Car
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
/**
* Car SQL映射器
* @version 1.0
* @since 1.0
*/
public interface CarMapper {
/**
* 根据id主键查询:结果最多只有⼀条
* @param id
* @return
*/
Car selectById(Long id); }
<?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.powernode.mybatis.mapper.CarMapper">
<select id="selectById" resultType="Car">
select id,car_num carNum,brand,guide_price guidePrice,produce_tim
e produceTime,car_type 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.junit.Test;
public class CarMapperTest {
@Test
public void testSelectById(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Car car = mapper.selectById(35L);
System.out.println(car);
}
}
执行结果 :
7.2 返回List<Car>
当查询的记录条数是多条的时候,必须使⽤集合接收。如果使⽤单个实体类接收会出现异常。
/**
* 查询所有的Car
* @return
*/
List<Car> selectAll();
<select id="selectAll" resultType="Car">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car
</select>
@Test
public void testSelectAll(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
List<Car> cars = mapper.selectAll();
cars.forEach(car -> System.out.println(car)); }
执行结果 :
如果返回多条记录,采⽤单个实体类接收会怎样?
7.3 返回Map
当返回的数据,没有合适的实体类对应的话,可以采⽤Map集合接收。字段名做key,字段值做value。 查询如果可以保证只有⼀条数据,则返回⼀个Map集合即可。
/**
* 通过id查询⼀条记录,返回Map集合
* @param id
* @return
*/
Map<String, Object> selectByIdRetMap(Long id);
<select id="selectByIdRetMap" resultType="map">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car where id = #{id}
</select>
resultMap=“map”,这是因为mybatis内置了很多别名。
@Test
public void testSelectByIdRetMap(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
Map<String,Object> car = mapper.selectByIdRetMap(35L);
System.out.println(car); }
执行结果 :
当然,如果返回⼀个Map集合,可以将Map集合放到List集合中吗?当然可以
反过来,如果返回的不是⼀条记录,是多条记录的话,只采⽤单个Map集合接收,这样同样会出现之前 的异常:TooManyResultsException
7.4 返回List<Map>
查询结果条数⼤于等于1条数据,则可以返回⼀个存储Map集合的List集合。List<Map>等同于List<Car>
/**
* 查询所有的Car,返回⼀个List集合。List集合中存储的是Map集合。
* @return
*/
List<Map<String,Object>> selectAllRetListMap();
<select id="selectAllRetListMap" resultType="map">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car
</select>
@Test
public void testSelectAllRetListMap(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Map<String,Object>> cars = mapper.selectAllRetListMap();
System.out.println(cars); }
执⾏结果:
[
{carType=燃油⻋, carNum=103, guidePrice=50.30, produceTime=2020-10-01, id=
33, brand=奔驰E300L},
{carType=电⻋, carNum=102, guidePrice=30.23, produceTime=2018-09-10, id=34
, brand=⽐亚迪汉},
{carType=燃油⻋, carNum=103, guidePrice=50.30, produceTime=2020-10-01, id=
35, brand=奔驰E300L},
{carType=燃油⻋, carNum=103, guidePrice=33.23, produceTime=2020-10-11, id=
36, brand=奔驰C200},
......
]
7.5 resultMap结果映射
-
查询结果的列名和java对象的属性名对应不上怎么办?
- 第⼀种⽅式:as 给列起别名
- 第⼆种⽅式:使⽤resultMap进⾏结果映射
- 第三种⽅式:是否开启驼峰命名⾃动映射(配置settings)
resultMap结果映射
/**
* 查询所有Car,使⽤resultMap进⾏结果映射
* @return
*/
List<Car> selectAllByResultMap();
<!--
resultMap:
id:这个结果映射的标识,作为select标签的resultMap属性的值。
type:结果集要映射的类。可以使⽤别名。
-->
<resultMap id="carResultMap" type="car">
<!--对象的唯⼀标识,官⽅解释是:为了提⾼mybatis的性能。建议写上。-->
<id property="id" column="id"/>
<result property="carNum" column="car_num"/>
<!--当属性名和数据库列名⼀致时,可以省略。但建议都写上。-->
<!--javaType⽤来指定属性类型。jdbcType⽤来指定列类型。⼀般可以省略。-->
<result property="brand" column="brand" />
<result property="guidePrice" column="guide_price"/>
<result property="produceTime" column="produce_time"/>
<result property="carType" column="car_type"/>
</resultMap>
<!--resultMap属性的值必须和resultMap标签中id属性值⼀致。-->
<select id="selectAllByResultMap" resultMap="carResultMap">
select * from t_car
</select>
@Test
public void testSelectAllByResultMap(){
CarMapper carMapper = SqlSessionUtil.openSession().getMapper(CarMapper.
class);
List<Car> cars = carMapper.selectAllByResultMap();
System.out.println(cars); }
执⾏结果正常
是否开启驼峰命名⾃动映射
使⽤这种⽅式的前提是:属性名遵循Java的命名规范,数据库表的列名遵循SQL的命名规范。
Java命名规范:⾸字⺟⼩写,后⾯每个单词⾸字⺟⼤写,遵循驼峰命名⽅式。
SQL命名规范:全部⼩写,单词之间采⽤下划线分割。
⽐如以下的对应关系:
如何启⽤该功能,在mybatis-config.xml⽂件中进⾏配置:
<!--放在properties标签后⾯-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
/**
* 查询所有Car,启⽤驼峰命名⾃动映射
* @return
*/
List<Car> selectAllByMapUnderscoreToCamelCase();
<select id="selectAllByMapUnderscoreToCamelCase" resultType="Car">
select * from t_car
</select>
@Test
public void testSelectAllByMapUnderscoreToCamelCase(){
CarMapper carMapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = carMapper.selectAllByMapUnderscoreToCamelCase();
System.out.println(cars); }
执⾏结果正常。
7.6 返回总记录条数
需求:查询总记录条数
/**
* 获取总记录条数
* @return
*/
Long selectTotal();
<!--long是别名,可参考mybatis开发⼿册。-->
<select id="selectTotal" resultType="long">
select count(*) from t_car
</select>
@Test
public void testSelectTotal(){
CarMapper carMapper = SqlSessionUtil.openSession().getMapper(CarMapper.
class);
Long total = carMapper.selectTotal();
System.out.println(total); }
第八章 动态SQL
有的业务场景,也需要SQL语句进⾏动态拼接,例如:
批量删除
delete from t_car where id in(1,2,3,4,5,6,......这⾥的值是动态的,根据⽤户选择的id不同,值是不同的);
- 多条件查询
select * from t_car where brand like '丰⽥%' and guide_price > 30 and .....;
8.1 if标签
需求:多条件查询。
可能的条件包括:品牌(brand)、指导价格(guide_price)、汽⻋类型(car_type)
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface CarMapper {
/**
* 根据多条件查询Car
* @param brand
* @param guidePrice
* @param carType
* @return
*/
List<Car> selectByMultiCondition(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType); }
<?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.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.junit.Test;
import java.util.List;
public class CarMapperTest {
@Test
public void testSelectByMultiCondition(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectByMultiCondition("丰⽥", 20.0, "燃油 ⻋");
System.out.println(cars);
}
}
执⾏结果:
如果第⼀个条件为空,剩下两个条件不为空,会是怎样呢?
List<Car> cars = mapper.selectByMultiCondition("", 20.0, "燃油⻋");
执⾏结果:
报错了,SQL语法有问题,where后⾯出现了and。这该怎么解决呢?
● 可以where后⾯添加⼀个恒成⽴的条件。
执⾏结果:
如果三个条件都是空,有影响吗?
List<Car> cars = mapper.selectByMultiCondition("", null, "");
执⾏结果:
三个条件都不为空呢?
List<Car> cars = mapper.selectByMultiCondition("丰⽥", 20.0, "燃油⻋");
执⾏结果:
8.2 where标签
where标签的作⽤:让where⼦句更加动态智能。 所有条件都为空时,where标签保证不会⽣成where⼦句。 ⾃动去除某些条件前⾯多余的and或or。
继续使⽤if标签中的需求。
/**
* 根据多条件查询Car,使⽤where标签
* @param brand
* @param guidePrice
* @param carType
* @return
*/
List<Car> selectByMultiConditionWithWhere(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
<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(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
List<Car> cars = mapper.selectByMultiConditionWithWhere("丰⽥", 20.0, "燃油⻋");
System.out.println(cars); }
运⾏结果:
如果所有条件都是空呢?
List<Car> cars = mapper.selectByMultiConditionWithWhere("", null, "");
执行结果 :
它可以⾃动去掉前⾯多余的and,那可以⾃动去掉前⾯多余的or吗?
List<Car> cars = mapper.selectByMultiConditionWithWhere("丰⽥", 20.0, "燃油 ⻋");
<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>
执⾏结果:
8.3 trim标签
- trim标签的属性:
prefix:在trim标签中的语句前添加内容
suffix:在trim标签中的语句后添加内容
prefixOverrides:前缀覆盖掉(去掉)
suffixOverrides:后缀覆盖掉(去掉)
/**
* 根据多条件查询Car,使⽤trim标签
* @param brand
* @param guidePrice
* @param carType
* @return
*/
List<Car> selectByMultiConditionWithTrim(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
<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(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectByMultiConditionWithTrim("丰⽥", 20.0, "") ;
System.out.println(cars); }
如果所有条件为空,where会被加上吗?
List<Car> cars = mapper.selectByMultiConditionWithTrim("", null, "");
执⾏结果:
8.4 set标签
主要使⽤在update语句当中,⽤来⽣成set关键字,同时去掉最后多余的“,”
⽐如我们只更新提交的不为空的字段,如果提交的数据是空或者"",那么这个字段我们将不更新
/**
* 更新信息,使⽤set标签
* @param car
* @return
*/
int updateWithSet(Car car);
<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},</i f>
</set>
where id = #{id}
</update>
@Test
public void testUpdateWithSet(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
Car car = new Car(38L,"1001","丰⽥霸道2",10.0,"",null);
int count = mapper.updateWithSet(car);
System.out.println(count);
SqlSessionUtil.openSession().commit(); }
执⾏结果:
8.5 choose when otherwise
这三个标签是在⼀起使⽤的:
<choose>
<when></when>
<when></when>
<when></when>
<otherwise></otherwise>
</choose>
等同于:
if(){
}else if(){
}else if(){
}else if(){
}else{ }
只有⼀个分⽀会被选择!!!!
需求:先根据品牌查询,如果没有提供品牌,再根据指导价格查询,如果没有提供指导价格,就根据⽣产⽇期查询。
/**
* 使⽤choose when otherwise标签查询
* @param brand
* @param guidePrice
* @param produceTime
* @return
*/
List<Car> selectWithChoose(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("produceTime") String produceTime);
<select id="selectWithChoose" 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 testSelectWithChoose(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
//List<Car> cars = mapper.selectWithChoose("丰⽥霸道", 20.0, "2000-10-1
0");
//List<Car> cars = mapper.selectWithChoose("", 20.0, "2000-10-10");
//List<Car> cars = mapper.selectWithChoose("", null, "2000-10-10");
List<Car> cars = mapper.selectWithChoose("", null, "");
System.out.println(cars); }
8.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; 12
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来删除
/**
* 通过foreach完成批量删除
* @param ids
* @return
*/
int deleteBatchByForeach(@Param("ids") Long[] ids);
<!--
collection:集合或数组
item:集合或数组中的元素
separator:分隔符
open:foreach标签中所有内容的开始
close:foreach标签中所有内容的结束
-->
<delete id="deleteBatchByForeach">
delete from t_car where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
@Test
public void testDeleteBatchByForeach(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
int count = mapper.deleteBatchByForeach(new Long[]{40L, 41L, 42L});
System.out.println("删除了⼏条记录:" + count);
SqlSessionUtil.openSession().commit(); }
执⾏结果:
- ⽤or来删除
/**
* 通过foreach完成批量删除
* @param ids
* @return
*/
int deleteBatchByForeach2(@Param("ids") Long[] ids);
<delete id="deleteBatchByForeach2">
delete from t_car where
<foreach collection="ids" item="id" separator="or">
id = #{id}
</foreach>
</delete>
@Test
public void testDeleteBatchByForeach2(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cla
ss);
int count = mapper.deleteBatchByForeach2(new Long[]{40L, 41L, 42L});
System.out.println("删除了⼏条记录:" + count);
SqlSessionUtil.openSession().commit(); }
执行结果 :
批量添加
/**
* 批量添加,使⽤foreach标签
* @param cars
* @return
*/
int insertBatchByForeach(@Param("cars") List<Car> cars);
<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(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.cl
ass);
Car car1 = new Car(null, "2001", "兰博基尼", 100.0, "1998-10-11", "燃油 ⻋");
Car car2 = new Car(null, "2001", "兰博基尼", 100.0, "1998-10-11", "燃油 ⻋");
Car car3 = new Car(null, "2001", "兰博基尼", 100.0, "1998-10-11", "燃油 ⻋");
List<Car> cars = Arrays.asList(car1, car2, car3);
int count = mapper.insertBatchByForeach(cars);
System.out.println("插⼊了⼏条记录" + count);
SqlSessionUtil.openSession().commit(); }
执⾏结果:
8.7 sql标签与include标签
sql标签⽤来声明sql⽚段
include标签⽤来将声明的sql⽚段包含到某个sql语句当中
作⽤:代码复⽤。易维护
<sql id="carCols">id,car_num carNum,brand,guide_price guidePrice,produce_t
ime produceTime,car_type carType</sql> <select id="selectAllRetMap" resultType="map">
select <include refid="carCols"/> from t_car
</select> <select id="selectAllRetListMap" resultType="map">
select <include refid="carCols"/> carType from t_car
</select> <select id="selectByIdRetMap" resultType="map">
select <include refid="carCols"/> from t_car where id = #{id}
</select>
第九章 MyBatis的⾼级映射及延迟加载
模块名:mybatis-009-advanced-mapping
打包⽅式:jar
依赖:mybatis依赖、mysql驱动依赖、junit依赖、logback依赖
配置⽂件:mybatis-config.xml、logback.xml、jdbc.properties
拷⻉⼯具类:SqlSessionUtil
准备数据库表:⼀个班级对应多个学⽣。班级表:t_clazz。学⽣表:t_student
创建pojo:Student、Clazz
package com.powernode.mybatis.pojo;
/**
* 学⽣类
* @version 1.0
* @since 1.0
*/
public class Student {
private Integer sid;
private String sname;
//......
}
package com.powernode.mybatis.pojo;
/**
* 班级类
* @version 1.0
* @since 1.0
*/
public class Clazz {
private Integer cid;
private String cname;
//......
}
创建mapper接⼝:StudentMapper、ClazzMapper
创建mapper映射⽂件:StudentMapper.xml、ClazzMapper.xml
9.1 多对一
多种⽅式,常⻅的包括三种:
● 第⼀种⽅式:⼀条SQL语句,级联属性映射。
● 第⼆种⽅式:⼀条SQL语句,association。
● 第三种⽅式:两条SQL语句,分步查询。(这种⽅式常⽤:优点⼀是可复⽤。优点⼆是⽀持懒加载。)
第一种方式: 级联属性映射
pojo类Student中添加⼀个属性:Clazz clazz; 表示学⽣关联的班级对象。
package com.powernode.mybatis.pojo;
/**
* 学⽣类
* @version 1.0
* @since 1.0
*/
public class Student {
private Integer sid;
private String sname;
//......
}
<?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.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 join t_clazz c on s.cid = c.cid where sid = #{sid}
</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.junit.Test;
public class StudentMapperTest {
@Test
public void testSelectBySid(){
StudentMapper mapper = SqlSessionUtil.openSession().getMapper(Stud
entMapper.class);
Student student = mapper.selectBySid(1);
System.out.println(student);
}
}
执行结果 :
第⼆种⽅式: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>
第三种⽅式:分步查询
其他位置不需要修改,只需要修改以及添加以下三处:
第⼀处:association中select位置填写sqlId。sqlId=namespace+id。其中column属性作为这条⼦sql语句的条件。
<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接⼝中添加⽅法
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Clazz;
/**
* Clazz映射器接⼝
* @version 1.0
* @since 1.0
*/
public interface ClazzMapper {
/**
* 根据cid获取Clazz信息
* @param cid
* @return
*/
Clazz selectByCid(Integer cid); }
第三处:在ClazzMapper.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.powernode.mybatis.mapper.ClazzMapper">
<select id="selectByCid" resultType="Clazz">
select * from t_clazz where cid = #{cid}
</select>
</mapper>
执⾏结果,可以很明显看到先后有两条sql语句执⾏:
分步优点:
第⼀个优点:代码复⽤性增强。
第⼆个优点:⽀持延迟加载。【暂时访问不到的数据可以先不查询。提⾼程序的执⾏效率。】
9.2 多对⼀延迟加载
在mybatis中如何开启全局的延迟加载呢?需要setting配置,如下:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
把fetchType="lazy"去掉。
执⾏以下程序:
public class StudentMapperTest {
@Test
public void testSelectBySid(){
StudentMapper mapper = SqlSessionUtil.openSession().getMapper(StudentMapper.class);
Student student = mapper.selectBySid(1);
//System.out.println(student);
// 只获取学⽣姓名
String sname = student.getSname();
System.out.println("学⽣姓名:" + sname);
// 到这⾥之后,想获取班级名字了
String cname = student.getClazz().getCname();
System.out.println("学⽣的班级名称:" + cname);
}
}
通过以上的测试可以看出,我们已经开启了全局延迟加载策略。
开启全局延迟加载之后,所有的sql都会⽀持延迟加载,如果某个sql你不希望它⽀持延迟加载怎么办呢?
将fetchType设置为eager:
<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,你就关闭了延迟加载机制。
后期我们要不要开启延迟加载机制,主要看实际的业务需求是怎样的。
9.3 ⼀对多
⼀对多的实现,通常是在⼀的⼀⽅中有List集合属性。
在Clazz类中添加List<Student> stus; 属性。
public class Clazz {
private Integer cid;
private String cname;
private List<Student> stus;
// set get⽅法
// 构造⽅法
// toString⽅法 }
⼀对多的实现通常包括两种实现⽅式:
- 第⼀种⽅式:collection
- 第⼆种⽅式:分步查询
第⼀种⽅式:collection
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Clazz;
/**
* Clazz映射器接⼝
* @version 1.0
* @since 1.0
*/
public interface ClazzMapper {
/**
* 根据cid获取Clazz信息
* @param cid
* @return
*/
Clazz selectByCid(Integer cid);
/**
* 根据班级编号查询班级信息。同时班级中所有的学⽣信息也要查询。
* @param cid
* @return
*/
Clazz selectClazzAndStusByCid(Integer cid); }
<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>
注意是ofType,表示“集合中的类型”
package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.ClazzMapper;
import com.powernode.mybatis.pojo.Clazz;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;
public class ClazzMapperTest {
@Test
public void testSelectClazzAndStusByCid() {
ClazzMapper mapper = SqlSessionUtil.openSession().getMapper(ClazzMapper.class);
Clazz clazz = mapper.selectClazzAndStusByCid(1001);
System.out.println(clazz);
}
}
执⾏结果:
第⼆种⽅式:分步查询(比较常用)
修改以下三个位置即可:
<resultMap id="clazzResultMap" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<!--主要看这⾥-->
<collection property="stus"
select="com.powernode.mybatis.mapper.StudentMapper.selectByC
id"
column="cid"/>
</resultMap>
<!--sql语句也变化了-->
<select id="selectClazzAndStusByCid" resultMap="clazzResultMap">
select * from t_clazz c where c.cid = #{cid}
</select>
/**
* 根据班级编号获取所有的学⽣。
* @param cid
* @return
*/
List<Student> selectByCid(Integer cid);
<select id="selectByCid" resultType="Student">
select * from t_student where cid = #{cid}
</select>
执⾏结果:
9.4 ⼀对多延迟加载
⼀对多延迟加载机制和多对⼀是⼀样的。同样是通过两种⽅式:
- 第⼀种:fetchType=“lazy”
- 第⼆种:修改全局的配置setting,lazyLoadingEnabled=true,如果开启全局延迟加载,想让某个 sql不使⽤延迟加载:fetchType=“eager”
第十章 MyBatis的缓存
缓存:cache
缓存的作⽤:通过减少IO的⽅式,来提⾼程序的执⾏效率。
mybatis的缓存:将select语句的查询结果放到缓存(内存)当中,下⼀次还是这条select语句的话,直 接从缓存中取,不再查数据库。⼀⽅⾯是减少了IO。另⼀⽅⾯不再执⾏繁琐的查找算法。效率⼤⼤提升。
mybatis缓存包括:
⼀级缓存:将查询到的数据存储到SqlSession中。
⼆级缓存:将查询到的数据存储到SqlSessionFactory中。
或者集成其它第三⽅的缓存:⽐如EhCache Java语⾔开发的】、Memcache【C语⾔开发的】 等
缓存只针对于DQL语句,也就是说缓存机制只对应select语句。
10.1 ⼀级缓存
⼀级缓存默认是开启的。不需要做任何配置。
原理:只要使⽤同⼀个SqlSession对象执⾏同⼀条SQL语句,就会⾛缓存
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
public interface CarMapper {
/**
* 根据id获取Car信息。
* @param id
* @return
*/
Car selectById(Long id); }
<?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.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 com.powernode.mybatis.utils.SqlSessionUtil;
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;
public class CarMapperTest {
@Test
public void testSelectById() throws Exception{
// 注意:不能使⽤我们封装的SqlSessionUtil⼯具类。
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = builder.build(Resources.getR
esourceAsStream("mybatis-config.xml"));
SqlSession sqlSession1 = sqlSessionFactory.openSession();
CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
Car car1 = mapper1.selectById(83L);
System.out.println(car1);
CarMapper mapper2 = sqlSession1.getMapper(CarMapper.class);
Car car2 = mapper2.selectById(83L);
System.out.println(car2);
SqlSession sqlSession2 = sqlSessionFactory.openSession();
CarMapper mapper3 = sqlSession2.getMapper(CarMapper.class);
Car car3 = mapper3.selectById(83L);
System.out.println(car3);
CarMapper mapper4 = sqlSession2.getMapper(CarMapper.class);
Car car4 = mapper4.selectById(83L);
System.out.println(car4);
}
}
执行结果 :
什么情况下不⾛缓存?
第⼀种:不同的SqlSession对象。
第⼆种:查询条件变化了。
⼀级缓存失效情况包括两种:
- 第⼀种:第⼀次查询和第⼆次查询之间,⼿动清空了⼀级缓存。
清空一级缓存
sqlSession.clearCache();
- 第⼆种:第⼀次查询和第⼆次查询之间,执⾏了增删改操作。【这个增删改和哪张表没有关系,只要 有insert delete update操作,⼀级缓存就失效。】
10.4 ⼆级缓存
⼆级缓存的范围是SqlSessionFactory。 使⽤⼆级缓存需要具备以下⼏个条件:
- 全局性地开启或关闭所有映射器配置⽂件中已配置的任何缓存。默认就是true,⽆需设置。
- 在需要使⽤⼆级缓存的SqlMapper.xml⽂件中添加配置:
- 使⽤⼆级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接⼝
- SqlSession对象关闭或提交之后,⼀级缓存中的数据才会被写⼊到⼆级缓存当中。此时⼆级缓存才可⽤。
测试⼆级缓存:
<cache/>
public class Car implements Serializable {
//......
}
@Test
public void testSelectById2() throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession1 = sqlSessionFactory.openSession();
CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
Car car1 = mapper1.selectById(83L);
System.out.println(car1);
// 关键⼀步
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
Car car2 = mapper2.selectById(83L);
System.out.println(car2); }
⼆级缓存的失效:只要两次查询之间出现了增删改操作。⼆级缓存就会失效。【⼀级缓存也会失效】
⼆级缓存的相关配置
- eviction:指定从缓存中移除某个对象的淘汰算法。默认采⽤LRU策略。
a. LRU:Least Recently Used。最近最少使⽤。优先淘汰在间隔时间内使⽤频率最低的对象。(其
实还有⼀种淘汰算法LFU,最不常⽤。)
b. FIFO:First In First Out。⼀种先进先出的数据缓存器。先进⼊⼆级缓存的对象最先被淘汰。
c. SOFT:软引⽤。淘汰软引⽤指向的对象。具体算法和JVM的垃圾回收算法有关。
d. WEAK:弱引⽤。淘汰弱引⽤指向的对象。具体算法和JVM的垃圾回收算法有关。
- flushInterval:
a. ⼆级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存,只要内存⾜够⼤,⼀
直会向⼆级缓存中缓存数据。除⾮执⾏了增删改。
-
readOnly:a. true:多条相同的sql语句执⾏之后返回的对象是共享的同⼀个。性能好。但是多线程并发可能
会存在安全问题。
b. false:多条相同的sql语句执⾏之后返回的对象是副本,调⽤了clone⽅法。性能⼀般。但安
全。
-
size:
a. 设置⼆级缓存中最多可存储的java对象数量。默认值1024。
第十一章 MyBatis的逆向⼯程
所谓的逆向⼯程是:根据数据库表逆向⽣成Java的pojo类,SqlMapper.xml⽂件,以及Mapper接⼝类 等。要完成这个⼯作,需要借助别⼈写好的逆向⼯程插件。
思考:使⽤这个插件的话,需要给这个插件配置哪些信息?
● pojo类名、包名以及⽣成位置。
● SqlMapper.xml⽂件名以及⽣成位置。
● Mapper接⼝名以及⽣成位置。
● 连接数据库的信息。
● 指定哪些表参与逆向⼯程。
● …
11.1 逆向⼯程配置与⽣成
第⼀步:基础环境准备
新建模块:mybatis-011-generator
打包⽅式:jar
第⼆步:在pom中添加逆向⼯程插件
<!--定制构建过程-->
<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
该⽂件名必须叫做:generatorConfig.xml
该⽂件必须放在类的根路径下。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//E
N"
"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.UnmergeableXmlMappersP
lugin"/>
<commentGenerator>
<!--是否去掉⽣成⽇期-->
<property name="suppressDate" value="true"/>
<!--是否去除注释-->
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--连接数据库信息-->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/powerno
de"
userId="root"
password="root">
</jdbcConnection>
<!-- ⽣成pojo包名和位置 -->
<javaModelGenerator targetPackage="com.powernode.mybatis.pojo" tar
getProject="src/main/java">
<!--是否开启⼦包-->
<property name="enableSubPackages" value="true"/>
<!--是否去除字段名的前后空⽩-->
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- ⽣成SQL映射⽂件的包名和位置 -->
<sqlMapGenerator targetPackage="com.powernode.mybatis.mapper" targ
etProject="src/main/resources">
<!--是否开启⼦包-->
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- ⽣成Mapper接⼝的包名和位置 -->
<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使⽤PageHelper
12.1 limit分⻚
mysql的limit后⾯两个数字:
第⼀个数字:startIndex(起始下标。下标从0开始。)
第⼆个数字:pageSize(每⻚显示的记录条数)
假设已知⻚码pageNum,还有每⻚显示的记录条数pageSize,第⼀个数字可以动态的获取吗?
startIndex = (pageNum - 1) * pageSize
所以,标准通⽤的mysql分⻚SQL:
select
*
from
tableName ......
limit
(pageNum - 1) * pageSize, pageSize
在MyBatis中使用
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface CarMapper {
/**
* 通过分⻚的⽅式获取Car列表
* @param startIndex ⻚码
* @param pageSize 每⻚显示记录条数
* @return
*/
List<Car> selectAllByPage(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize);
}
<?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.powernode.mybatis.mapper.CarMapper">
<select id="selectAllByPage" 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 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.util.List;
public class PageTest {
@Test
public void testPage()throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
// ⻚码
Integer pageNum = 2;
// 每⻚显示记录条数
Integer pageSize = 3;
// 起始下标
Integer startIndex = (pageNum - 1) * pageSize;
List<Car> cars = mapper.selectAllByPage(startIndex, pageSize);
cars.forEach(car -> System.out.println(car));
sqlSession.commit();
sqlSession.close();
} }
执⾏结果:
12.2 PageHelper插件
使⽤PageHelper插件进⾏分⻚,更加的便捷。
第⼀步:引⼊依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.1</version>
</dependency>
第⼆步:在mybatis-config.xml⽂件中配置插件
typeAliases标签下⾯进⾏配置:
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
第三步:编写Java代码
List<Car> selectAll();
<select id="selectAll" resultType="Car">
select * from t_car
</select>
关键点:
- 在查询语句之前开启分⻚功能。
- 在查询语句之后封装PageInfo对象。(PageInfo对象将来会存储到request域当中。在⻚⾯上展示。)
@Test
public void testPageHelper() throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().b
uild(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
// 开启分⻚
PageHelper.startPage(2, 2);
// 执⾏查询语句
List<Car> cars = mapper.selectAll();
// 获取分⻚信息对象
PageInfo<Car> pageInfo = new PageInfo<>(cars, 5);
System.out.println(pageInfo); }
执⾏结果:
PageInfo{pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=6, pages=3,
list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=6, pages=3,
reasonable=false, pageSizeZero=false}[Car{id=86, carNum=‘1234’, brand=‘丰⽥霸道’,
guidePrice=50.5, produceTime=‘2020-10-11’, carType=‘燃油⻋’}, Car{id=87, carNum=‘1234’,
brand=‘丰⽥霸道’, guidePrice=50.5, produceTime=‘2020-10-11’, carType=‘燃油⻋’}], prePage=1,
nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true,
navigatePages=5, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]}
对执⾏结果进⾏格式化:
PageInfo{
pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=6, pages=3,
list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=
6, pages=3, reasonable=false, pageSizeZero=false}
[Car{id=86, carNum='1234', brand='丰⽥霸道', guidePrice=50.5, produceTime
='2020-10-11', carType='燃油⻋'},
Car{id=87, carNum='1234', brand='丰⽥霸道', guidePrice=50.5, produceTime
='2020-10-11', carType='燃油⻋'}],
prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPa
ge=true, hasNextPage=true,
navigatePages=5, navigateFirstPage=1, navigateLastPage=3, navigatepageNum
s=[1, 2, 3]
}