一、背景
1、什么是CRUD?
- 通常我们用CRUD代表增删改查四种操作。
- C:Create、R:Relative、U:Update、D:Delete。
2、在本篇内容学习之前,需要一些准备工作:
- 创建一个新的模块【利用Maven构建的普通Java模块】
- 修改 pom.xml 中的一些配置信息
- 添加一条语句,将打包方式定义为 jar 包 【因为我们当前不需要在web服务器上运行我们的项目】
- 添加 mybatis、mysql驱动、junit、logback的依赖
<!--打了一个jar包-->
<packaging>jar</packaging>
<dependencies>
<!--mybatis核心依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
<scope>import</scope>
</dependency>
<!--mysql驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
<scope>import</scope>
</dependency>
<!--添加junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!--导入日志文件的依赖-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
<scope>test</scope>
</dependency>
</dependencies>
- 将 mybatis-config.xml、logback.xml 文件 放到根路径下
(1) 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>
<!--日志配置-->
<settings>
<setting name="logImpl" value="SLF4J"/>
</settings>
<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/powernode"/>
<!--配置自己的数据库用户名和密码-->
<property name="username" value="root"/>
<property name="password" value="111111"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--sql映射文件创建好之后,需要将该文件路径配置到这里[对应的Mapper文件]-->
<mapper resource="CarMapper.xml"/>
</mappers>
</configuration>
(2) logback.xml 文件的配置信息如下
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<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"/>
<!-- 日志输出级别,logback日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR -->
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
- 提供com.powernode.mybatis.utils.SqlSessionUtil工具类【方便创建会话】
package com.powernode.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;
/**
* @author Bonbons
* @version 1.0
*/
public class SqlSessionUtil {
private SqlSessionUtil(){}
//定义一个SqlSession
private static final SqlSessionFactory sqlSessionFactory;
//在类加载的时候初始化SqlSessionFactory
static {
try {
//此处自己的 mybatis-config.xml 文件位置要配置正确
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//通过一个公有的方法为外部提供会话的对象
public static SqlSession openSession(){
return sqlSessionFactory.openSession(true);
}
}
- 最后一步就是创建测试用例
二、Insert【添加数据】
1、前情回顾:
- 之前我们的 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">
<insert id="insertCar">
insert into t_car(car_num,brand,guide_price,produce_time,car_type) values('103', '奔驰E300L', 50.3, '2022-01-01', '燃油车')
</insert>
</mapper>
插入数据的语句是固定的,如果想插入不同的数据,只能在这块自己调整参数 >> 很不方便
2、改进方法1:
(1)在MyBatis中可以将数据放到Map集合中,在使用Java程序处理业务时,将Map对象作为参数传递进来。
(2)在sql语句中使用 #{map集合的key}
来完成传值,其中的 #{} 就是占位符
(3)给出改进后的 CarMapper.xml 中的配置信息【可以看出此时占位符中的参数已经替换为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="com.powernode.CarMapperTest">
<!--insert sql:保存一个汽车信息-->
<insert id="insertCar">
insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values (null,#{car_num},#{brand},#{guide_price},#{produce_time},#{car_type})
</insert>
</mapper>
(4)再看一下测试用例中的代码:
@Test
public void testInsertCar(){
//利用工具类创建会话
SqlSession session = SqlSessionUtil.openSession();
//创建一个Map存储我们要传递的信息
Map<String, Object> map = new HashMap<String, Object>();
// id,car_num,brand,guide_price,produce_time,car_type
// 对于key的选取应该见名知意
map.put("car_num", 3);
map.put("brand", "奥迪");
map.put("guide_price", 50.0);
map.put("produce_time", "2022-10-18");
map.put("car_type", "燃油车");
//调用我们的添加方法
session.insert("insertCar", map);
//提交事务
session.commit();
session.close();
}
(5)运行后可以刷新 Navicat 查看结果
3、改进方法2:
(1)设置一个普通的Java类,类的属性对应数据库中的字段,为外部提供Get和Set方法【该系列将这种普通Java类称之为pojo】
(2)创建一个 com.powernode.pojo.Car 类,用于封装数据
package com.powernode.mybatis.pojo;
/**
* @author Bonbons
* @version 1.0
*/
public class Car {
// id,car_num,brand,guide_price,produce_time,car_type
// 使用包装类,出现null时不会报错
// 对于这些私有属性要与表中数据对应上
private Long id;
private String carNum;
private String brand;
private Double guidePrice;
private String produceTime;
private String carType;
//提供构造方法
public Car(){}
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;
}
//提供get和set方法
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;
}
//重写toString方法
@Override
public String toString() {
return "Car{" +
"id=" + id +
", carNum='" + carNum + '\'' +
", brand='" + brand + '\'' +
", guidePrice=" + guidePrice +
", produceTime='" + produceTime + '\'' +
", carType='" + carType + '\'' +
'}';
}
}
(2)要修改 CarMapper.xml 文件,更改 insert 方法中占位符中的内容
- 这里面需要注意一个问题,占位符中的内容并不一定是Car类中的参数,它这个对应机制是这样的:
#{parameter} --> getParemeter() 【也就是说会去Car类中寻找这个get方法来获取参数】
<?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="com.powernode.CarMapperTest">
<!--insert sql:保存一个汽车信息-->
<insert id="insertCar">
insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values (null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
</mapper>
(3)原来的测试类中添加相关的的测试方法
@Test
public void testInsertCarByPOJO(){
SqlSession session = SqlSessionUtil.openSession();
//封装数据
Car car = new Car(null, "4", "托马斯", 200.0, "2022-11-11","煤油车");
//事务处理
session.insert("insertCar", car);
//提交事务
session.commit();
session.close();
}
4、总结
- 如果采用map集合传参,#{} 里写的是map集合的key,如果key不存在不会报错,数据库表中会插入NULL。
- 如果采用POJO传参,#{} 里写的是get方法的方法名去掉get之后将剩下的单词首字母变小写【getAge对应的是 #{age}】
三、delete 和 update【删除、更新数据】
- 此处我们只涉及简单的删除和更新操作,重点在于了解基本语法
- 我们根据 id 来完成数据的删除和更新
- 此部分给出在 CarMapper.xml 中的配置信息以及测试类中的对应测试方法
1、通过指定 id 删除数据
(1)在CarMapper.xml 中的配置信息如下【为了看起来更简洁,只给出对应的SQL语句】
- 有一件值得注意的事情:当只存在一个占位符时,可以自动识别,参数名不匹配也没有问题
<!--根据id删除数据-->
<delete id="deleteById">
delete from t_car where id = #{id}
</delete>
它的外部代码是这样的
<?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="balabala">
<!--在这部分写我们的SQL语句-->
</mapper>
(2)测试方法如下【也是只给出了完整方法,没去写类名】
@Test
public void testDeleteCarById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
// count会记录操作实际影响数据库表中数据的条数
int count = sqlSession.delete("deleteById", 15);
//通过变量名.sout 可以快速生成输出指定变量的语句[在IDEA中]
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
(3)成功运行,删除了原来 id = 15 的数据
2、通过指定 id 更新数据
(1)在CarMapper.xml 中的配置信息如下
<!--根据id修改数据-->
<update id="updateById">
update t_car set car_num = #{carNum}, brand = #{brand}, guide_price = #{guidePrice}, produce_time = #{produceTime},car_type = #{carType} where id = #{id}
</update>
(2)测试方法如下
@Test
public void testUpdateCarById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
Car car = new Car(17L, "1111", "奔驰大G", 200.0, "2020-01-01", "汽油越野车");
int count = sqlSession.update("updateById", car);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
(3)成功运行,修改了原来 id = 17 的数据
四、Select【查询数据】
- 查询语句的重点在于:对于查询结果集的处理以及类属性名与数据库字段名的对应
- 此处分别以查询一条数据和查询全部数据为例,来说明MyBatis中是如何完成数据查询的
1、 根据 id 查询一条数据:
(1)CarMapper中的配置信息
<select id="selectById" resultType="com.powernode.mybatis.pojo.Car">
<!--select * from t_car where id = #{id}-->
select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where id = #{id}
</select>
细心的盆友不难发现,查询语句与其他数据操作SQL语句不同:
第一,多了resultType;第二为查询的字段设置了别名
- 使用 resultType 是为了告诉 MyBatis 我们查询结果集是一个什么类型的数据【使用的是全限定类名(类的完整地址)】
- 设置别名,是因为如果类属性与数据库表中字段对应不上,就无法正确传递参数值
(2)测试方法
@Test
public void testSelectById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
//用Car的实例接收返回值
Car car = sqlSession.selectOne("selectById",1);
//打印查询结果[之所以此处可以输出属性和值,是因为在Car类中重写了toString方法]
System.out.println(car);
//查询语句不需要提交事务
sqlSession.close();
}
2、查询全部的数据
- 与前者的区别在于:查询时不需要设置条件、返回的是一个结果的集合
(1)CarMapper中的配置信息
<!--查询数据库表中全部数据-->
<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car
</select>
(2)测试方法
@Test
public void testSelectAll(){
SqlSession sqlSession = SqlSessionUtil.openSession();
List<Car> cars = sqlSession.selectList("selectAll");
//使用Lamda表达式+forEach输出
cars.forEach(car -> System.out.println(car));
sqlSession.close();
}
3、namespace 用法说明
- 当出现两个 XxxMapper.xml 文件中出现同ID的SQL语句时,可能会出现问题
- 我们可以通过
namespace.SQL语句id
来指定使用哪个Mapper中的 SQL 方法 - 简言之,命名空间 >> 防止 id 冲突
- PS:如果设置了多个Mapper.xml 文件,要记得添加到核心配置文件中