概述
文章目录
- mybatis 创建基础查询
- 一、mybatis 入门
- 二、Mapper 代理开发
- 三.核心配置文件
- 1 environments
- 2 导入db 配置文件
- 3 别名
- 4.创建utils工具类
- 四、使用:Lombok
- 配置Lombok
- 使用详解
- 部分常用参数解析
- 五、重构基础查询
- 六:resultMap映射器
- 七:条件查询
- 1. 根据id查询
- 2. 增
- 3. 删
- 4. 改
- 八:复杂查询
- 九:缓存机制
- 黑马Mybatis
- 1,配置文件实现CRUD
- 1.1 环境准备
- 1.2 查询所有数据
- 1.2.1 编写接口方法
- 1.2.2 编写SQL语句
- 1.2.3 编写测试方法
- 1.2.4 起别名解决上述问题
- 1.2.5 使用resultMap解决上述问题
- 1.2.6 小结
- 1.3 查询详情
- 1.3.1 编写接口方法
- 1.3.2 编写SQL语句
- 1.3.3 编写测试方法
- 1.3.4 参数占位符
- 1.3.5 parameterType使用
- 1.3.6 SQL语句中特殊字段处理
- 1.4 多条件查询
- 1.4.1 编写接口方法
- 1.4.2 编写SQL语句
- 1.4.3 编写测试方法
- 1.4.4 动态SQL
- 1.5 单个条件(动态SQL)
- 1.5.1 编写接口方法
- 1.5.2 编写SQL语句
- 1.5.3 编写测试方法
- 1.6 添加数据
- 1.6.1 编写接口方法
- 1.6.2 编写SQL语句
- 1.6.3 编写测试方法
- 1.6.4 添加-主键返回
- 1.7 修改
- 1.7.1 编写接口方法
- 1.7.2 编写SQL语句
- 1.7.3 编写测试方法
- 1.8 删除一行数据
- 1.8.1 编写接口方法
- 1.8.2 编写SQL语句
- 1.8.3 编写测试方法
- 1.9 批量删除
- 1.9.1 编写接口方法
- 1.9.2 编写SQL语句
- 1.9.3 编写测试方法
- 1.10 Mybatis参数传递
- 1.10.1 多个参数
- 1.10.2 单个参数
- 2,注解实现CRUD
- Maven
- 简介
- 作用
- 基本概念
- 仓库
- 坐标
- 配置本地仓库
- IDEA创建Maven项目
- 1.不使用原型创建
- 2.原型创建Java项目
- 3.原型创建web项目
- 添加Tomcat插件
- 依赖管理
- 生命周期与插件
- Maven继承关系
- Maven常用命令
- Maven测试项目
- Maven测试项目
- Junit5
- JUL日志系统
- JUL日志讲解
- Properties配置文件
- 编写日志配置文件
- 使用Lombok快速开启日志
- Mybatis日志系统
- Tomcat
- IDEA 创建Web项目
- Servlet
- 创建Servlet
- 探究Servlet的生命周期
- 解读和使用HttpServlet
- @WebServlet注解详解
- 使用POST请求完成登陆
- 连接数据库校验用户登录
- 上传和下载文件
- 使用XHR请求数据
- 重定向与请求转发
- 了解ServletContext对象
- 初始化参数
- 会话
- Cookie
- Session
- Filter
- Listener
- 使用Thymeleaf模板引擎
- Thymeleaf语法基础
- Thymeleaf流程控制语法
- Thymeleaf模板布局
- 探讨Tomcat类加载机制
本笔记个人整理自黑马2022、青空霞光,仅供学习使用
mybatis 创建基础查询
一、mybatis 入门
1.创建user表,添加数据
create database mybatis;
use mybatis;
drop table if exists tb_user;
create table tb_user(
id int primary key auto_increment,
username varchar(20),
password varchar(20),
gender char(1),
addr varchar(30)
);
INSERT INTO tb_user VALUES (1, 'zhangsan', '123', '男', '北京');
INSERT INTO tb_user VALUES (2, '李四', '234', '女', '天津');
INSERT INTO tb_user VALUES (3, '王五', '11', '男', '西安');
2.pom.xml
创建模块,导入坐标
<dependencies>
<!-- mybatis 依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!-- mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!-- juit 单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- 添加slf4j日志api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.20</version>
</dependency>
<!-- 添加logback-classic依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- 添加logback-core依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
3.src/main/resources
创建logback.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%level] %blue(%d{HH:mm:ss.SSS}) %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>
</encoder>
</appender>
<logger name="com.itheima" level="DEBUG" additivity="false">
<appender-ref ref="Console"/>
</logger>
<!--
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
, 默认debug
<root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
-->
<root level="DEBUG">
<appender-ref ref="Console"/>
</root>
</configuration>
4.src/main/resources
编写核心配置文件
<?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:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment
-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--加载sql映射文件-->
<mapper resource="com/itheima/mapper/UserMapper.xml"/>
</mappers>
</configuration>
5.编写SQL映射文件
<select id="selectAll" resultType="com.itheima.pojo.User">
select *
from tb_user;
</select>
6.mybatis-config.xml
加载映射文件
<!--加载sql映射文件-->
<mapper resource="UserMapper.xml"/>
7.创建POJO实体类 java/com.itheima.pojo/User.java
package com.itheima.pojo;
public class User {
private Integer id;
private String username;
private String password;
private String gender;
private String addr;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + ''' +
", password='" + password + ''' +
", gender='" + gender + ''' +
", addr='" + addr + ''' +
'}';
}
}
8.创建测试类
package com.itheima;
import com.itheima.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* Mybatis 快速入门代码
*/
public class MyBatisDemo {
public static void main(String[] args) throws IOException {
//1. 加载mybatis的核心配置文件,获取 SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象,用它来执行sql
SqlSession sqlSession = sqlSessionFactory.openSession();
//3. 执行sql
List<User> users = sqlSession.selectList("test.selectAll");
System.out.println(users);
//4. 释放资源
sqlSession.close();
}
}
二、Mapper 代理开发
使用Mapper代理方式完成入门案例
1.定义与SQL映射文件同名的Mapper接口,并且将Mapper接口与SQL映射文件放置在同一目录下
2.设置SQL映射文件的namespace属性为Mapper接口全限定名
3.在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致
4.编码
1.通过SqlSession 的 getMapper 方法获取 Mapper 接口的代理对象
2.调用对应方法完成sql的执行
如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载
1.1 创建mapper.UserMapper.java
接口
package com.itheima.mapper;
public interface UserMapper {
}
1.2在resources
目录下创建目录com.itheima.mapper
注意:不要用.
创建目录,使用/
将UserMapper.xml
移动到该文件夹下
2.设置SQL映射文件的namespace属性为Mapper接口全限定名
User.Mapper.xml
<mapper namespace="com.itheima.mapper.UserMapper">
3.在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致
UserMapper.java
接口中定义查询方法
List<User> selectAll();
3.2 修改mybatis-config.xml
的sql映射文件路径
<mappers>
<!--加载sql映射文件-->
<mapper resource="com/itheima/mapper/UserMapper.xml"/>
</mappers>
4.src/com/itheima
创建MybatisDemo2
核心
//3.1 获取UserMapper接口的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectAll();
完整代码
/**
* Mybatis 代理开发
*/
public class MyBatisDemo2 {
public static void main(String[] args) throws IOException {
//1. 加载mybatis的核心配置文件,获取 SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象,用它来执行sql
SqlSession sqlSession = sqlSessionFactory.openSession();
//3. 执行sql
//List<User> users = sqlSession.selectList("test.selectAll");
//3.1 获取UserMapper接口的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectAll();
System.out.println(users);
//4. 释放资源
sqlSession.close();
}
}
5.Mapper代理更改为包扫描方式
加载sql映射文件,每个文件都要写 不方便
更改为代理方式
mapper接口与xml都放到正确的位置时,可以使用包扫描自动加载
<mappers>
<!--加载sql映射文件-->
<!-- <mapper resource="com/itheima/mapper/UserMapper.xml"/>-->
<!--Mapper代理方式-->
<package name="com.itheima.mapper"/>
</mappers>
三.核心配置文件
1 environments
environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment
配置不同的数据源,将来可以通过<environment id="development">
来切换
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
2 导入db 配置文件
创建db.properties
文件
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
mysql.username=root
mysql.password=1234
<properties resource="db.properties"/>
mybatis-config.xml
引入数据
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
</environments>
3 别名
mybatis-config.xml
<!-- 别名 -->
<typeAliases>
<package name="com.itheima.pojo"/>
<!-- 该包下的文件不区分大小写-->
</typeAliases>
起完别名之后 Mapper 映射文件下的resultType
可直接写类名,且不区分大小写
4.创建utils工具类
src/main/com/itheima/utils/MyBatisUtil.java
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
/**
* 工具类
*/
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory = null;
// 初始化SqlSessionFactory对象
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取SqlSession对象的静态方法
public static SqlSessionFactory getSqlSessionFactory() {
return sqlSessionFactory;
}
}
注意:
SqlSession sqlSession = sqlSessionFactory.openSession()
不能写到工具类里面,不能让所有功能都共用一个连接,这样对管理事务不好
四、使用:Lombok
Lombok下载
用来简化get/set方法,无需担心字段名更改而更改对应的get/set方法
配置Lombok
1.下载lombok.jar
拖入到项目文件夹下–> 添加为库
或者在pom.xml添加依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
2.重启IDEA
使用详解
我们通过添加@Getter
和@Setter
来为当前类的所有字段生成get/set
方法,注意静态字段不会生成,final字段无法生成set方法。
我们还可以使用@Accessors
来控制生成Getter和Setter的样式。
我们通过添加@ToString
来为当前类生成预设的toString方法。
我们可以通过添加@EqualsAndHashCode
来快速生成比较和哈希值方法。
我们可以通过添加@AllArgsConstructor
和@NoArgsConstructor
来快速生成全参构造和无参构造。
我们可以添加@RequiredArgsConstructor
来快速生成参数只包含final或被标记为@NonNull
的成员字段。
使用@Data
能代表@Setter
、@Getter
、@RequiredArgsConstructor
、@ToString
、@EqualsAndHashCode
全部注解。
一旦使用@Data
就不建议此类有继承关系,因为equal方法可能不符合预期结果(尤其是仅比较子类属性)。
使用@Value
与@Data
类似,但是并不会生成setter并且成员属性都是final的。
使用@SneakyThrows
来自动生成try-catch代码块。
使用@Cleanup
作用与局部变量,在最后自动调用其close()方法(可以自由更换)
使用@Builder
来快速生成建造者模式。
通过使用@Builder.Default
来指定默认值。
通过使用@Builder.ObtainVia
来指定默认值的获取方式。
部分常用参数解析
链式调用
@Accessors(chain = true)
public class Student {
private Integer id;
private String name;
private Integer age;
}
例:
Student student = new Student();
student.setId(1).setAge(18).setName("zs");
toString不包含字段名
@ToString(includeFieldNames = false)
省略打印字段
@ToString(exclude = {"id","name"}) // of是只包含
Include 作用在字段上 rank 排序,值越大越前 name给字段重命名输出
五、重构基础查询
(引入lombok,utils,使用db配置文件)
- 创建maven 项目,引入依赖
略… - 建立项目结构
- 在
main/resources
创建以下配置文件
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>
<!-- 导入db配置文件-->
<properties resource="db.properties"/>
<!-- 别名 -->
<typeAliases>
<package name="com.itheima.pojo"/>
</typeAliases>
<!--
environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment
-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--Mapper代理方式-->
<package name="com.itheima.mapper"/>
</mappers>
</configuration>
- pojo 创建实体类User
@Data
public class User {
private int uid;
private String uname;
private int uage;
}
5.mapper 创建UserMapper接口
public interface UserMapper {
List<User> selectAll();
}
6.main/resources
创建目录 com/itheima/mapper
,创建UserMapper.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.itheima.mapper.UserMapper">
<select id="selectAll" resultType="user">
select * from users;
</select>
</mapper>
7.utils 创建工具类 MyBatisUtils 8.在test文件夹下创建
com.itheima.test.UserMapperTest.java`
public class UserMapperTest {
SqlSessionFactory sqlSessionFactory = MyBatisUtils.getSqlSessionFactory();
@Test
public void testSelectAll() {
try(SqlSession sqlSession = sqlSessionFactory.openSession()){
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.selectAll();
System.out.println(users);
}
}
}
六:resultMap映射器
主要作用:数据库字段名与实体类名不一致,需要编写映射规则
<!-- resultMap写法 -->
<resultMap id="StudentMap" type="student">
<result column="sid" property="id"/>
<result column="sname" property="name"/>
<result column="sage" property="age"/>
</resultMap>
<select id="selectAll" resultType="student" resultMap="StudentMap">
select *
from t_student;
</select>
- 注解代替resultMap写法 StudentMapper.java
@Select("select * from t_student")
@Results({
@Result(id=true,column="sid",property="id"),
@Result(column="sname",property="name"),
@Result(column="sage",property="age")
})
- 别名写法
<!-- 别名写法-->
<select id="selectAll" resultType="student">
select sid as id,sname as name,sage as age
from t_student;
</select>
- 实体类使用@Alias
@Data
@Alias("tbUser")
public class User {
private int uid;
private String uname;
private int uage;
}
<select id="selectAll" resultType="tbUser">
select * from users;
</select>
七:条件查询
1. 根据id查询
User selectById(int id);
<resultMap id="userMap" type="user">
<result column="uid" property="id"/>
<result column="uname" property="name"/>
<result column="uage" property="age"/>
</resultMap>
<select id="selectById" resultMap="userMap">
select * from users where uid = #{id}
</select>
我们通过使用#{xxx}
或是${xxx}
来填入我们给定的属性,实际上Mybatis本质也是通过PreparedStatement首先进行一次预编译,有效地防止SQL注入问题,但是如果使用${xxx}
就不再是通过预编译,而是直接传值,因此我们一般都使用#{xxx}
来进行操作
如果数据库中存在一个带下划线的字段,我们可以通过设置让其映射为以驼峰命名的字段,比如my_test映射为myTest
在mybatis-config.xml
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
2. 增
<insert id="add">
insert into users(uname, uage) values (#{name},#{age})
</insert>
3. 删
<delete id="deleteById">
delete from users where uid = #{id}
</delete>
4. 改
<update id="update">
update users
set uname = #{name},
uage = #{age}
where uid = #{id}
</update>
八:复杂查询
摘自 Ketuer
一个老师可以教授多个学生,那么能否一次性将老师的学生全部映射给此老师的对象呢,比如:
@Data
public class Teacher {
int tid;
String name;
List<Student> studentList;
}
映射为Teacher对象时,同时将其教授的所有学生一并映射为List列表,显然这是一种一对多的查询,那么这时就需要进行复杂查询了。而我们之前编写的都非常简单,直接就能完成映射,因此我们现在需要使用resultMap
来自定义映射规则:
<select id="getTeacherByTid" resultMap="asTeacher">
select *, teacher.name as tname from student inner join teach on student.sid = teach.sid
inner join teacher on teach.tid = teacher.tid where teach.tid = #{tid}
</select>
<resultMap id="asTeacher" type="Teacher">
<id column="tid" property="tid"/>
<result column="tname" property="name"/>
<collection property="studentList" ofType="Student">
<id property="sid" column="sid"/>
<result column="name" property="name"/>
<result column="sex" property="sex"/>
</collection>
</resultMap>
可以看到,我们的查询结果是一个多表联查的结果,而联查的数据就是我们需要映射的数据(比如这里是一个老师有N个学生,联查的结果也是这一个老师对应N个学生的N条记录),其中<id column="tid" property="tid"/>
标签用于在多条记录中辨别是否为同一个对象的数据,比如上面的查询语句得到的结果中,tid
这一行始终为1
,因此所有的记录都应该是tid=1
的教师的数据,而不应该变为多个教师的数据,如果不加id进行约束,那么会被识别成多个教师的数据!
通过使用collection来表示将得到的所有结果合并为一个集合,比如上面的数据中每个学生都有单独的一条记录,因此tid相同的全部学生的记录就可以最后合并为一个List,得到最终的映射结果,当然,为了区分,最好也设置一个id,只不过这个例子中可以当做普通的result
使用。
了解了一对多,那么多对一又该如何查询呢,比如每个学生都有一个对应的老师,现在Student新增了一个Teacher对象,那么现在又该如何去处理呢?
@Data
@Accessors(chain = true)
public class Student {
private int sid;
private String name;
private String sex;
private Teacher teacher;
}
@Data
public class Teacher {
int tid;
String name;
}
现在我们希望的是,每次查询到一个Student对象时都带上它的老师,同样的,我们也可以使用resultMap
来实现(先修改一下老师的类定义,不然会很麻烦):
<resultMap id="test2" type="Student">
<id column="sid" property="sid"/>
<result column="name" property="name"/>
<result column="sex" property="sex"/>
<association property="teacher" javaType="Teacher">
<id column="tid" property="tid"/>
<result column="tname" property="name"/>
</association>
</resultMap>
<select id="selectStudent" resultMap="test2">
select *, teacher.name as tname from student left join teach on student.sid = teach.sid
left join teacher on teach.tid = teacher.tid
</select>
通过使用association
进行关联,形成多对一的关系,实际上和一对多是同理的,都是对查询结果的一种处理方式罢了。
九:缓存机制
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。
n
其实缓存机制我们在之前学习IO流的时候已经提及过了,我们可以提前将一部分内容放入缓存,下次需要获取数据时,我们就可以直接从缓存中读取,这样的话相当于直接从内存中获取而不是再去向数据库索要数据,效率会更高。
因此Mybatis内置了一个缓存机制,我们查询时,如果缓存中存在数据,那么我们就可以直接从缓存中获取,而不是再去向数据库进行请求。
Mybatis存在一级缓存和二级缓存,我们首先来看一下一级缓存,默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存(一级缓存无法关闭,只能调整),我们来看看下面这段代码:
public static void main(String[] args) throws InterruptedException {
try (SqlSession sqlSession = MybatisUtil.getSession(true)){
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
Student student1 = testMapper.getStudentBySid(1);
Student student2 = testMapper.getStudentBySid(1);
System.out.println(student1 == student2);
}
}
我们发现,两次得到的是同一个Student对象,也就是说我们第二次查询并没有重新去构造对象,而是直接得到之前创建好的对象。如果还不是很明显,我们可以修改一下实体类:
@Data
@Accessors(chain = true)
public class Student {
public Student(){
System.out.println("我被构造了");
}
private int sid;
private String name;
private String sex;
}
我们通过前面的学习得知Mybatis在映射为对象时,在只有一个构造方法的情况下,无论你构造方法写成什么样子,都会去调用一次构造方法,如果存在多个构造方法,那么就会去找匹配的构造方法。我们可以通过查看构造方法来验证对象被创建了几次。
结果显而易见,只创建了一次,也就是说当第二次进行同样的查询时,会直接使用第一次的结果,因为第一次的结果已经被缓存了。
那么如果我修改了数据库中的内容,缓存还会生效吗:
public static void main(String[] args) throws InterruptedException {
try (SqlSession sqlSession = MybatisUtil.getSession(true)){
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
Student student1 = testMapper.getStudentBySid(1);
testMapper.addStudent(new Student().setName("小李").setSex("男"));
Student student2 = testMapper.getStudentBySid(1);
System.out.println(student1 == student2);
}
}
我们发现,当我们进行了插入操作后,缓存就没有生效了,我们再次进行查询得到的是一个新创建的对象。
也就是说,一级缓存,在进行DML操作后,会使得缓存失效,也就是说Mybatis知道我们对数据库里面的数据进行了修改,所以之前缓存的内容可能就不是当前数据库里面最新的内容了。还有一种情况就是,当前会话结束后,也会清理全部的缓存,因为已经不会再用到了。但是一定注意,一级缓存只针对于单个会话,多个会话之间不相通。
public static void main(String[] args) {
try (SqlSession sqlSession = MybatisUtil.getSession(true)){
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
Student student2;
try(SqlSession sqlSession2 = MybatisUtil.getSession(true)){
TestMapper testMapper2 = sqlSession2.getMapper(TestMapper.class);
student2 = testMapper2.getStudentBySid(1);
}
Student student1 = testMapper.getStudentBySid(1);
System.out.println(student1 == student2);
}
}
**注意:**一个会话DML操作只会重置当前会话的缓存,不会重置其他会话的缓存,也就是说,其他会话缓存是不会更新的!
一级缓存给我们提供了很高速的访问效率,但是它的作用范围实在是有限,如果一个会话结束,那么之前的缓存就全部失效了,但是我们希望缓存能够扩展到所有会话都能使用,因此我们可以通过二级缓存来实现,二级缓存默认是关闭状态,要开启二级缓存,我们需要在映射器XML文件中添加:
<cache/>
可见二级缓存是Mapper级别的,也就是说,当一个会话失效时,它的缓存依然会存在于二级缓存中,因此如果我们再次创建一个新的会话会直接使用之前的缓存,我们首先根据官方文档进行一些配置:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
我们来编写一个代码:
public static void main(String[] args) {
Student student;
try (SqlSession sqlSession = MybatisUtil.getSession(true)){
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
student = testMapper.getStudentBySid(1);
}
try (SqlSession sqlSession2 = MybatisUtil.getSession(true)){
TestMapper testMapper2 = sqlSession2.getMapper(TestMapper.class);
Student student2 = testMapper2.getStudentBySid(1);
System.out.println(student2 == student);
}
}
我们可以看到,上面的代码中首先是第一个会话在进行读操作,完成后会结束会话,而第二个操作重新创建了一个新的会话,再次执行了同样的查询,我们发现得到的依然是缓存的结果。
那么如果我不希望某个方法开启缓存呢?我们可以添加useCache属性来关闭缓存:
<select id="getStudentBySid" resultType="Student" useCache="false">
select * from student where sid = #{sid}
</select>
我们也可以使用flushCache="false"在每次执行后都清空缓存,通过这这个我们还可以控制DML操作完成之后不清空缓存。
<select id="getStudentBySid" resultType="Student" flushCache="true">
select * from student where sid = #{sid}
</select>
添加了二级缓存之后,会先从二级缓存中查找数据,当二级缓存中没有时,才会从一级缓存中获取,当一级缓存中都还没有数据时,才会请求数据库,因此我们再来执行上面的代码:
public static void main(String[] args) {
try (SqlSession sqlSession = MybatisUtil.getSession(true)){
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
Student student2;
try(SqlSession sqlSession2 = MybatisUtil.getSession(true)){
TestMapper testMapper2 = sqlSession2.getMapper(TestMapper.class);
student2 = testMapper2.getStudentBySid(1);
}
Student student1 = testMapper.getStudentBySid(1);
System.out.println(student1 == student2);
}
}
得到的结果就会是同一个对象了,因为现在是优先从二级缓存中获取。
读取顺序:二级缓存 => 一级缓存 => 数据库
虽然缓存机制给我们提供了很大的性能提升,但是缓存存在一个问题,我们之前在计算机组成原理
中可能学习过缓存一致性问题,也就是说当多个CPU在操作自己的缓存时,可能会出现各自的缓存内容不同步的问题,而Mybatis也会这样,我们来看看这个例子:
public static void main(String[] args) throws InterruptedException {
try (SqlSession sqlSession = MybatisUtil.getSession(true)){
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
while (true){
Thread.sleep(3000);
System.out.println(testMapper.getStudentBySid(1));
}
}
}
我们现在循环地每三秒读取一次,而在这个过程中,我们使用IDEA手动修改数据库中的数据,将1号同学的学号改成100,那么理想情况下,下一次读取将无法获取到小明,因为小明的学号已经发生变化了。
但是结果却是依然能够读取,并且sid并没有发生改变,这也证明了Mybatis的缓存在生效,因为我们是从外部进行修改,Mybatis不知道我们修改了数据,所以依然在使用缓存中的数据,但是这样很明显是不正确的,因此,如果存在多台服务器或者是多个程序都在使用Mybatis操作同一个数据库,并且都开启了缓存,需要解决这个问题,要么就得关闭Mybatis的缓存来保证一致性:
<settings>
<setting name="cacheEnabled" value="false"/>
</settings>
<select id="getStudentBySid" resultType="Student" useCache="false" flushCache="true">
select * from student where sid = #{sid}
</select>
要么就需要实现缓存共用,也就是让所有的Mybatis都使用同一个缓存进行数据存取,在后面,我们会继续学习Redis、Ehcache、Memcache等缓存框架,通过使用这些工具,就能够很好地解决缓存一致性问题。
黑马Mybatis
1,配置文件实现CRUD
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fZVENlcy-1648965526161)(F:JavaEEnoteJavaEEMybatisJavaWeb_Mybatis.assetsimage-20210729111159534.png)]
如上图所示产品原型,里面包含了品牌数据的 查询
、按条件查询
、添加
、删除
、批量删除
、修改
等功能,而这些功能其实就是对数据库表中的数据进行CRUD操作。接下来我们就使用Mybatis完成品牌数据的增删改查操作。以下是我们要完成功能列表:
- 查询
- 查询所有数据
- 查询详情
- 条件查询
- 添加
- 修改
- 修改全部字段
- 修改动态字段
- 删除
- 删除一个
- 批量删除
我们先将必要的环境准备一下。
1.1 环境准备
-
数据库表(tb_brand)及数据准备
-- 删除tb_brand表 drop table if exists tb_brand; -- 创建tb_brand表 create table tb_brand ( -- id 主键 id int primary key auto_increment, -- 品牌名称 brand_name varchar(20), -- 企业名称 company_name varchar(20), -- 排序字段 ordered int, -- 描述信息 description varchar(100), -- 状态:0:禁用 1:启用 status int ); -- 添加数据 insert into tb_brand (brand_name, company_name, ordered, description, status) values ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0), ('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界', 1), ('小米', '小米科技有限公司', 50, 'are you ok', 1);
-
实体类 Brand
在
com.itheima.pojo
包下创建 Brand 实体类。public class Brand { // id 主键 private Integer id; // 品牌名称 private String brandName; // 企业名称 private String companyName; // 排序字段 private Integer ordered; // 描述信息 private String description; // 状态:0:禁用 1:启用 private Integer status; //省略 setter and getter。自己写时要补全这部分代码 }
-
编写测试用例
测试代码需要在
test/java
目录下创建包及测试用例。项目结构如下: -
安装 MyBatisX 插件
-
MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。
-
主要功能
- XML映射配置文件 和 接口方法 间相互跳转
- 根据接口方法生成 statement
-
安装方式
点击
file
,选择settings
,就能看到如下图所示界面注意:安装完毕后需要重启IDEA
-
插件效果
红色头绳的表示映射配置文件,蓝色头绳的表示mapper接口。在mapper接口点击红色头绳的小鸟图标会自动跳转到对应的映射配置文件,在映射配置文件中点击蓝色头绳的小鸟图标会自动跳转到对应的mapper接口。也可以在mapper接口中定义方法,自动生成映射配置文件中的
statement
,如图所示
-
1.2 查询所有数据
如上图所示就页面上展示的数据,而这些数据需要从数据库进行查询。接下来我们就来讲查询所有数据功能,而实现该功能我们分以下步骤进行实现:
-
编写接口方法:Mapper接口
-
参数:无
查询所有数据功能是不需要根据任何条件进行查询的,所以此方法不需要参数。
-
结果:List
我们会将查询出来的每一条数据封装成一个
Brand
对象,而多条数据封装多个Brand
对象,需要将这些对象封装到List集合中返回。 -
执行方法、测试
-
1.2.1 编写接口方法
在 com.itheima.mapper
包写创建名为 BrandMapper
的接口。并在该接口中定义 List<Brand> selectAll()
方法。
public interface BrandMapper {
/**
* 查询所有
*/
List<Brand> selectAll();
}
1.2.2 编写SQL语句
在 reources
下创建 com/itheima/mapper
目录结构,并在该目录下创建名为 BrandMapper.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.itheima.mapper.BrandMapper">
<select id="selectAll" resultType="brand">
select *
from tb_brand;
</select>
</mapper>
1.2.3 编写测试方法
在 MybatisTest
类中编写测试查询所有的方法
@Test
public void testSelectAll() throws IOException {
//1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//3. 获取Mapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//4. 执行方法
List<Brand> brands = brandMapper.selectAll();
System.out.println(brands);
//5. 释放资源
sqlSession.close();
}
注意:现在我们感觉测试这部分代码写起来特别麻烦,我们可以先忍忍。以后我们只会写上面的第3步的代码,其他的都不需要我们来完成。
执行测试方法结果如下:
从上面结果我们看到了问题,有些数据封装成功了,而有些数据并没有封装成功。为什么这样呢?
这个问题可以通过两种方式进行解决:
- 给字段起别名
- 使用resultMap定义字段和属性的映射关系
1.2.4 起别名解决上述问题
从上面结果可以看到 brandName
和 companyName
这两个属性的数据没有封装成功,查询 实体类 和 表中的字段 发现,在实体类中属性名是 brandName
和 companyName
,而表中的字段名为 brand_name
和 company_name
,如下图所示 。那么我们只需要保持这两部分的名称一致这个问题就迎刃而解。
我们可以在写sql语句时给这两个字段起别名,将别名定义成和属性名一致即可。
<select id="selectAll" resultType="brand">
select
id, brand_name as brandName, company_name as companyName, ordered, description, status
from tb_brand;
</select>
而上面的SQL语句中的字段列表书写麻烦,如果表中还有更多的字段,同时其他的功能也需要查询这些字段时就显得我们的代码不够精炼。Mybatis提供了sql
片段可以提高sql的复用性。
SQL片段:
-
将需要复用的SQL片段抽取到
sql
标签中<sql id="brand_column"> id, brand_name as brandName, company_name as companyName, ordered, description, status </sql>
id属性值是唯一标识,引用时也是通过该值进行引用。
-
在原sql语句中进行引用
使用
include
标签引用上述的 SQL 片段,而refid
指定上述 SQL 片段的id值。<select id="selectAll" resultType="brand"> select <include refid="brand_column" /> from tb_brand; </select>
1.2.5 使用resultMap解决上述问题
起别名 + sql片段的方式可以解决上述问题,但是它也存在问题。如果还有功能只需要查询部分字段,而不是查询所有字段,那么我们就需要再定义一个 SQL 片段,这就显得不是那么灵活。
那么我们也可以使用resultMap来定义字段和属性的映射关系的方式解决上述问题。
-
在映射配置文件中使用resultMap定义 字段 和 属性 的映射关系
<resultMap id="brandResultMap" type="brand"> <!-- id:完成主键字段的映射 column:表的列名 property:实体类的属性名 result:完成一般字段的映射 column:表的列名 property:实体类的属性名 --> <result column="brand_name" property="brandName"/> <result column="company_name" property="companyName"/> </resultMap>
注意:在上面只需要定义 字段名 和 属性名 不一样的映射,而一样的则不需要专门定义出来。
-
SQL语句正常编写
<select id="selectAll" resultMap="brandResultMap"> select * from tb_brand; </select>
1.2.6 小结
实体类属性名 和 数据库表列名 不一致,不能自动封装数据
- ==起别名:==在SQL语句中,对不一样的列名起别名,别名和实体类属性名一样
- 可以定义 片段,提升复用性
- ==resultMap:==定义 完成不一致的属性名和列名的映射
而我们最终选择使用 resultMap的方式。查询映射配置文件中查询所有的 statement 书写如下:
<resultMap id="brandResultMap" type="brand">
<!--
id:完成主键字段的映射
column:表的列名
property:实体类的属性名
result:完成一般字段的映射
column:表的列名
property:实体类的属性名
-->
<result column="brand_name" property="brandName"/>
<result column="company_name" property="companyName"/>
</resultMap>
<select id="selectAll" resultMap="brandResultMap">
select *
from tb_brand;
</select>
1.3 查询详情
有些数据的属性比较多,在页面表格中无法全部实现,而只会显示部分,而其他属性数据的查询可以通过 查看详情
来进行查询,如上图所示。
查看详情功能实现步骤:
-
编写接口方法:Mapper接口
-
参数:id
查看详情就是查询某一行数据,所以需要根据id进行查询。而id以后是由页面传递过来。
-
结果:Brand
根据id查询出来的数据只要一条,而将一条数据封装成一个Brand对象即可
-
-
编写SQL语句:SQL映射文件
-
执行方法、进行测试
1.3.1 编写接口方法
在 BrandMapper
接口中定义根据id查询数据的方法
/**
* 查看详情:根据Id查询
*/
Brand selectById(int id);
1.3.2 编写SQL语句
在 BrandMapper.xml
映射配置文件中编写 statement
,使用 resultMap
而不是使用 resultType
<select id="selectById" resultMap="brandResultMap">
select *
from tb_brand where id = #{id};
</select>
注意:上述SQL中的 #{id}先这样写,一会我们再详细讲解
1.3.3 编写测试方法
在 test/java
下的 com.itheima.mapper
包下的 MybatisTest类中
定义测试方法
@Test
public void testSelectById() throws IOException {
//接收参数,该id以后需要传递过来
int id = 1;
//1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//3. 获取Mapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//4. 执行方法
Brand brand = brandMapper.selectById(id);
System.out.println(brand);
//5. 释放资源
sqlSession.close();
}
执行测试方法结果如下:
1.3.4 参数占位符
查询到的结果很好理解就是id为1的这行数据。而这里我们需要看控制台显示的SQL语句,能看到使用?进行占位。说明我们在映射配置文件中的写的 #{id}
最终会被?进行占位。接下来我们就聊聊映射配置文件中的参数占位符。
mybatis提供了两种参数占位符:
-
#{} :执行SQL时,会将 #{} 占位符替换为?,将来自动设置参数值。从上述例子可以看出使用#{} 底层使用的是
PreparedStatement
-
${} :拼接SQL。底层使用的是
Statement
,会存在SQL注入问题。如下图将 映射配置文件中的 #{} 替换成 ${} 来看效果<select id="selectById" resultMap="brandResultMap"> select * from tb_brand where id = ${id}; </select>
重新运行查看结果如下:
==注意:==从上面两个例子可以看出,以后开发我们使用 #{} 参数占位符。
1.3.5 parameterType使用
对于有参数的mapper接口方法,我们在映射配置文件中应该配置 ParameterType
来指定参数类型。只不过该属性都可以省略。如下图:
<select id="selectById" parameterType="int" resultMap="brandResultMap">
select *
from tb_brand where id = ${id};
</select>
1.3.6 SQL语句中特殊字段处理
以后肯定会在SQL语句中写一下特殊字符,比如某一个字段大于某个值,如下图
可以看出报错了,因为映射配置文件是xml类型的问题,而 > < 等这些字符在xml中有特殊含义,所以此时我们需要将这些符号进行转义,可以使用以下两种方式进行转义
-
转义字符
下图的
<
就是<
的转义字符。 - <![CDATA[内容]]>
1.4 多条件查询
我们经常会遇到如上图所示的多条件查询,将多条件查询的结果展示在下方的数据列表中。而我们做这个功能需要分析最终的SQL语句应该是什么样,思考两个问题
- 条件表达式
- 如何连接
条件字段 企业名称
和 品牌名称
需要进行模糊查询,所以条件应该是:
简单的分析后,我们来看功能实现的步骤:
-
编写接口方法
- 参数:所有查询条件
- 结果:List
-
在映射配置文件中编写SQL语句
-
编写测试方法并执行
1.4.1 编写接口方法
在 BrandMapper
接口中定义多条件查询的方法。
而该功能有三个参数,我们就需要考虑定义接口时,参数应该如何定义。Mybatis针对多参数有多种实现
-
使用
@Param("参数名称")
标记每一个参数,在映射配置文件中就需要使用#{参数名称}
进行占位List<Brand> selectByCondition(@Param("status") int status, @Param("companyName") String companyName,@Param("brandName") String brandName);
-
将多个参数封装成一个 实体对象 ,将该实体对象作为接口的方法参数。该方式要求在映射配置文件的SQL中使用
#{内容}
时,里面的内容必须和实体类属性名保持一致。List<Brand> selectByCondition(Brand brand);
-
将多个参数封装到map集合中,将map集合作为接口的方法参数。该方式要求在映射配置文件的SQL中使用
#{内容}
时,里面的内容必须和map集合中键的名称一致。List<Brand> selectByCondition(Map map);
1.4.2 编写SQL语句
在 BrandMapper.xml
映射配置文件中编写 statement
,使用 resultMap
而不是使用 resultType
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
where status = #{status}
and company_name like #{companyName}
and brand_name like #{brandName}
</select>
1.4.3 编写测试方法
在 test/java
下的 com.itheima.mapper
包下的 MybatisTest类中
定义测试方法
@Test
public void testSelectByCondition() throws IOException {
//接收参数
int status = 1;
String companyName = "华为";
String brandName = "华为";
// 处理参数
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";
//1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//3. 获取Mapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//4. 执行方法
//方式一 :接口方法参数使用 @Param 方式调用的方法
//List<Brand> brands = brandMapper.selectByCondition(status, companyName, brandName);
//方式二 :接口方法参数是 实体类对象 方式调用的方法
//封装对象
/* Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);*/
//List<Brand> brands = brandMapper.selectByCondition(brand);
//方式三 :接口方法参数是 map集合对象 方式调用的方法
Map map = new HashMap();
map.put("status" , status);
map.put("companyName", companyName);
map.put("brandName" , brandName);
List<Brand> brands = brandMapper.selectByCondition(map);
System.out.println(brands);
//5. 释放资源
sqlSession.close();
}
1.4.4 动态SQL
上述功能实现存在很大的问题。用户在输入条件时,肯定不会所有的条件都填写,这个时候我们的SQL语句就不能那样写的
例如用户只输入 当前状态 时,SQL语句就是
select * from tb_brand where status = #{status}
而用户如果只输入企业名称时,SQL语句就是
select * from tb_brand where company_name like #{companName}
而用户如果输入了 当前状态
和 企业名称
时,SQL语句又不一样
select * from tb_brand where status = #{status} and company_name like #{companName}
针对上述的需要,Mybatis对动态SQL有很强大的支撑:
if
choose (when, otherwise)
trim (where, set)
foreach
我们先学习 if 标签和 where 标签:
-
if 标签:条件判断
- test 属性:逻辑表达式
<select id="selectByCondition" resultMap="brandResultMap"> select * from tb_brand where <if test="status != null"> and status = #{status} </if> <if test="companyName != null and companyName != '' "> and company_name like #{companyName} </if> <if test="brandName != null and brandName != '' "> and brand_name like #{brandName} </if> </select>
如上的这种SQL语句就会根据传递的参数值进行动态的拼接。如果此时status和companyName有值那么就会值拼接这两个条件。
执行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AwWyOrZ1-1648965526163)(F:JavaEEnoteJavaEEMybatisJavaWeb_Mybatis.assetsimage-20210729212510291.png)]
但是它也存在问题,如果此时给的参数值是
Map map = new HashMap(); // map.put("status" , status); map.put("companyName", companyName); map.put("brandName" , brandName);
拼接的SQL语句就变成了
select * from tb_brand where and company_name like ? and brand_name like ?
而上面的语句中 where 关键后直接跟 and 关键字,这就是一条错误的SQL语句。这个就可以使用 where 标签解决
-
where 标签
- 作用:
- 替换where关键字
- 会动态的去掉第一个条件前的 and
- 如果所有的参数没有值则不加where关键字
<select id="selectByCondition" resultMap="brandResultMap"> select * from tb_brand <where> <if test="status != null"> and status = #{status} </if> <if test="companyName != null and companyName != '' "> and company_name like #{companyName} </if> <if test="brandName != null and brandName != '' "> and brand_name like #{brandName} </if> </where> </select>
注意:需要给每个条件前都加上 and 关键字。
- 作用:
1.5 单个条件(动态SQL)
如上图所示,在查询时只能选择 品牌名称
、当前状态
、企业名称
这三个条件中的一个,但是用户到底选择哪儿一个,我们并不能确定。这种就属于单个条件的动态SQL语句。
这种需求需要使用到 choose(when,otherwise)标签
实现, 而 choose
标签类似于Java 中的switch语句。
通过一个案例来使用这些标签
1.5.1 编写接口方法
在 BrandMapper
接口中定义单条件查询的方法。
/**
* 单条件动态查询
* @param brand
* @return
*/
List<Brand> selectByConditionSingle(Brand brand);
1.5.2 编写SQL语句
在 BrandMapper.xml
映射配置文件中编写 statement
,使用 resultMap
而不是使用 resultType
<select id="selectByConditionSingle" resultMap="brandResultMap">
select *
from tb_brand
<where>
<choose><!--相当于switch-->
<when test="status != null"><!--相当于case-->
status = #{status}
</when>
<when test="companyName != null and companyName != '' "><!--相当于case-->
company_name like #{companyName}
</when>
<when test="brandName != null and brandName != ''"><!--相当于case-->
brand_name like #{brandName}
</when>
</choose>
</where>
</select>
1.5.3 编写测试方法
在 test/java
下的 com.itheima.mapper
包下的 MybatisTest类中
定义测试方法
@Test
public void testSelectByConditionSingle() throws IOException {
//接收参数
int status = 1;
String companyName = "华为";
String brandName = "华为";
// 处理参数
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";
//封装对象
Brand brand = new Brand();
//brand.setStatus(status);
brand.setCompanyName(companyName);
//brand.setBrandName(brandName);
//1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//3. 获取Mapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//4. 执行方法
List<Brand> brands = brandMapper.selectByConditionSingle(brand);
System.out.println(brands);
//5. 释放资源
sqlSession.close();
}
执行测试方法结果如下:
1.6 添加数据
如上图是我们平时在添加数据时展示的页面,而我们在该页面输入想要的数据后添加 提交
按钮,就会将这些数据添加到数据库中。接下来我们就来实现添加数据的操作。
-
编写接口方法
参数:除了id之外的所有的数据。id对应的是表中主键值,而主键我们是 自动增长 生成的。
-
编写SQL语句
-
编写测试方法并执行
明确了该功能实现的步骤后,接下来我们进行具体的操作。
1.6.1 编写接口方法
在 BrandMapper
接口中定义添加方法。
/**
* 添加
*/
void add(Brand brand);
1.6.2 编写SQL语句
在 BrandMapper.xml
映射配置文件中编写添加数据的 statement
<insert id="add">
insert into tb_brand (brand_name, company_name, ordered, description, status)
values (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status});
</insert>
1.6.3 编写测试方法
在 test/java
下的 com.itheima.mapper
包下的 MybatisTest类中
定义测试方法
@Test
public void testAdd() throws IOException {
//接收参数
int status = 1;
String companyName = "波导手机";
String brandName = "波导";
String description = "手机中的战斗机";
int ordered = 100;
//封装对象
Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);
brand.setDescription(description);
brand.setOrdered(ordered);
//1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//SqlSession sqlSession = sqlSessionFactory.openSession(true); //设置自动提交事务,这种情况不需要手动提交事务了
//3. 获取Mapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//4. 执行方法
brandMapper.add(brand);
//提交事务
sqlSession.commit();
//5. 释放资源
sqlSession.close();
}
执行结果如下:
1.6.4 添加-主键返回
在数据添加成功后,有时候需要获取插入数据库数据的主键(主键是自增长)。
比如:添加订单和订单项,如下图就是京东上的订单
订单数据存储在订单表中,订单项存储在订单项表中。
-
添加订单数据
-
添加订单项数据,订单项中需要设置所属订单的id
明白了什么时候 主键返回
。接下来我们简单模拟一下,在添加完数据后打印id属性值,能打印出来说明已经获取到了。
我们将上面添加品牌数据的案例中映射配置文件里 statement
进行修改,如下
<insert id="add" useGeneratedKeys="true" keyProperty="id">
insert into tb_brand (brand_name, company_name, ordered, description, status)
values (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status});
</insert>
在 insert 标签上添加如下属性:
- useGeneratedKeys:是够获取自动增长的主键值。true表示获取
- keyProperty :指定将获取到的主键值封装到哪儿个属性里
1.7 修改
如图所示是修改页面,用户在该页面书写需要修改的数据,点击 提交
按钮,就会将数据库中对应的数据进行修改。注意一点,如果哪儿个输入框没有输入内容,我们是将表中数据对应字段值替换为空白还是保留字段之前的值?答案肯定是保留之前的数据。
接下来我们就具体来实现
1.7.1 编写接口方法
在 BrandMapper
接口中定义修改方法。
/**
* 修改
*/
void update(Brand brand);
上述方法参数 Brand 就是封装了需要修改的数据,而id肯定是有数据的,这也是和添加方法的区别。
1.7.2 编写SQL语句
在 BrandMapper.xml
映射配置文件中编写修改数据的 statement
。
<update id="update">
update tb_brand
<set>
<if test="brandName != null and brandName != ''">
brand_name = #{brandName},
</if>
<if test="companyName != null and companyName != ''">
company_name = #{companyName},
</if>
<if test="ordered != null">
ordered = #{ordered},
</if>
<if test="description != null and description != ''">
description = #{description},
</if>
<if test="status != null">
status = #{status}
</if>
</set>
where id = #{id};
</update>
set 标签可以用于动态包含需要更新的列,忽略其它不更新的列。
1.7.3 编写测试方法
在 test/java
下的 com.itheima.mapper
包下的 MybatisTest类中
定义测试方法
@Test
public void testUpdate() throws IOException {
//接收参数
int status = 0;
String companyName = "波导手机";
String brandName = "波导";
String description = "波导手机,手机中的战斗机";
int ordered = 200;
int id = 6;
//封装对象
Brand brand = new Brand();
brand.setStatus(status);
// brand.setCompanyName(companyName);
// brand.setBrandName(brandName);
// brand.setDescription(description);
// brand.setOrdered(ordered);
brand.setId(id);
//1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//SqlSession sqlSession = sqlSessionFactory.openSession(true);
//3. 获取Mapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//4. 执行方法
int count = brandMapper.update(brand);
System.out.println(count);
//提交事务
sqlSession.commit();
//5. 释放资源
sqlSession.close();
}
执行测试方法结果如下:
从结果中SQL语句可以看出,只修改了 status
字段值,因为我们给的数据中只给Brand实体对象的 status
属性设置值了。这就是 set
标签的作用。
1.8 删除一行数据
如上图所示,每行数据后面都有一个 删除
按钮,当用户点击了该按钮,就会将改行数据删除掉。那我们就需要思考,这种删除是根据什么进行删除呢?是通过主键id删除,因为id是表中数据的唯一标识。
接下来就来实现该功能。
1.8.1 编写接口方法
在 BrandMapper
接口中定义根据id删除方法。
/**
* 根据id删除
*/
void deleteById(int id);
1.8.2 编写SQL语句
在 BrandMapper.xml
映射配置文件中编写删除一行数据的 statement
<delete id="deleteById">
delete from tb_brand where id = #{id};
</delete>
1.8.3 编写测试方法
在 test/java
下的 com.itheima.mapper
包下的 MybatisTest类中
定义测试方法
@Test
public void testDeleteById() throws IOException {
//接收参数
int id = 6;
//1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//SqlSession sqlSession = sqlSessionFactory.openSession(true);
//3. 获取Mapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//4. 执行方法
brandMapper.deleteById(id);
//提交事务
sqlSession.commit();
//5. 释放资源
sqlSession.close();
}
运行过程只要没报错,直接到数据库查询数据是否还存在。
1.9 批量删除
如上图所示,用户可以选择多条数据,然后点击上面的 删除
按钮,就会删除数据库中对应的多行数据。
1.9.1 编写接口方法
在 BrandMapper
接口中定义删除多行数据的方法。
/**
* 批量删除
*/
void deleteByIds(int[] ids);
参数是一个数组,数组中存储的是多条数据的id
1.9.2 编写SQL语句
在 BrandMapper.xml
映射配置文件中编写删除多条数据的 statement
。
编写SQL时需要遍历数组来拼接SQL语句。Mybatis 提供了 foreach
标签供我们使用
foreach 标签
用来迭代任何可迭代的对象(如数组,集合)。
- collection 属性:
- mybatis会将数组参数,封装为一个Map集合。
- 默认:array = 数组
- 使用@Param注解改变map集合的默认key的名称
- mybatis会将数组参数,封装为一个Map集合。
- item 属性:本次迭代获取到的元素。
- separator 属性:集合项迭代之间的分隔符。
foreach
标签不会错误地添加多余的分隔符。也就是最后一次迭代不会加分隔符。 - open 属性:该属性值是在拼接SQL语句之前拼接的语句,只会拼接一次
- close 属性:该属性值是在拼接SQL语句拼接后拼接的语句,只会拼接一次
<delete id="deleteByIds">
delete from tb_brand where id
in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
;
</delete>
假如数组中的id数据是{1,2,3},那么拼接后的sql语句就是:
delete from tb_brand where id in (1,2,3);
1.9.3 编写测试方法
在 test/java
下的 com.itheima.mapper
包下的 MybatisTest类中
定义测试方法
@Test
public void testDeleteByIds() throws IOException {
//接收参数
int[] ids = {5,7,8};
//1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//SqlSession sqlSession = sqlSessionFactory.openSession(true);
//3. 获取Mapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//4. 执行方法
brandMapper.deleteByIds(ids);
//提交事务
sqlSession.commit();
//5. 释放资源
sqlSession.close();
}
1.10 Mybatis参数传递
Mybatis 接口方法中可以接收各种各样的参数,如下:
- 多个参数
- 单个参数:单个参数又可以是如下类型
- POJO 类型
- Map 集合类型
- Collection 集合类型
- List 集合类型
- Array 类型
- 其他类型
1.10.1 多个参数
如下面的代码,就是接收两个参数,而接收多个参数需要使用 @Param
注解,那么为什么要加该注解呢?这个问题要弄明白就必须来研究Mybatis 底层对于这些参数是如何处理的。
User select(@Param("username") String username,@Param("password") String password);
<select id="select" resultType="user">
select *
from tb_user
where
username=#{username}
and password=#{password}
</select>
我们在接口方法中定义多个参数,Mybatis 会将这些参数封装成 Map 集合对象,值就是参数值,而键在没有使用 @Param
注解时有以下命名规则:
-
以 arg 开头 :第一个参数就叫 arg0,第二个参数就叫 arg1,以此类推。如:
map.put(“arg0”,参数值1);
map.put(“arg1”,参数值2);
-
以 param 开头 : 第一个参数就叫 param1,第二个参数就叫 param2,依次类推。如:
map.put(“param1”,参数值1);
map.put(“param2”,参数值2);
代码验证:
-
在
UserMapper
接口中定义如下方法User select(String username,String password);
-
在
UserMapper.xml
映射配置文件中定义SQL<select id="select" resultType="user"> select * from tb_user where username=#{arg0} and password=#{arg1} </select>
或者
<select id="select" resultType="user"> select * from tb_user where username=#{param1} and password=#{param2} </select>
-
运行代码结果如下
在映射配合文件的SQL语句中使用用
arg
开头的和param
书写,代码的可读性会变的特别差,此时可以使用@Param
注解。
在接口方法参数上使用 @Param
注解,Mybatis 会将 arg
开头的键名替换为对应注解的属性值。
代码验证:
-
在
UserMapper
接口中定义如下方法,在username
参数前加上@Param
注解User select(@Param("username") String username, String password);
Mybatis 在封装 Map 集合时,键名就会变成如下:
map.put(“username”,参数值1);
map.put(“arg1”,参数值2);
map.put(“param1”,参数值1);
map.put(“param2”,参数值2);
-
在
UserMapper.xml
映射配置文件中定义SQL<select id="select" resultType="user"> select * from tb_user where username=#{username} and password=#{param2} </select>
-
运行程序结果没有报错。而如果将
#{}
中的username
还是写成arg0
<select id="select" resultType="user"> select * from tb_user where username=#{arg0} and password=#{param2} </select>
-
运行程序则可以看到错误
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZvTCyMlr-1648965526165)(F:JavaEEnoteJavaEEMybatisJavaWeb_Mybatis.assetsimage-20210805231727206.png)]
结论:以后接口参数是多个时,在每个参数上都使用 @Param
注解。这样代码的可读性更高。
1.10.2 单个参数
-
POJO 类型
直接使用。要求
属性名
和参数占位符名称
一致 -
Map 集合类型
直接使用。要求
map集合的键名
和参数占位符名称
一致 -
Collection 集合类型
Mybatis 会将集合封装到 map 集合中,如下:
map.put(“arg0”,collection集合);
map.put(“collection”,collection集合;
可以使用
@Param
注解替换map集合中默认的 arg 键名。 -
List 集合类型
Mybatis 会将集合封装到 map 集合中,如下:
map.put(“arg0”,list集合);
map.put(“collection”,list集合);
map.put(“list”,list集合);
可以使用
@Param
注解替换map集合中默认的 arg 键名。 -
Array 类型
Mybatis 会将集合封装到 map 集合中,如下:
map.put(“arg0”,数组);
map.put(“array”,数组);
可以使用
@Param
注解替换map集合中默认的 arg 键名。 -
其他类型
比如int类型,
参数占位符名称
叫什么都可以。尽量做到见名知意
2,注解实现CRUD
使用注解开发会比配置文件开发更加方便。如下就是使用注解进行开发
@Select(value = "select * from tb_user where id = #{id}")
public User select(int id);
注意:
- 注解是用来替换映射配置文件方式配置的,所以使用了注解,就不需要再映射配置文件中书写对应的
statement
Mybatis 针对 CURD 操作都提供了对应的注解,已经做到见名知意。如下:
- 查询 :@Select
- 添加 :@Insert
- 修改 :@Update
- 删除 :@Delete
接下来我们做一个案例来使用 Mybatis 的注解开发
代码实现:
-
将之前案例中
UserMapper.xml
中的 根据id查询数据 的statement
注释掉 -
在
UserMapper
接口的selectById
方法上添加注解 -
运行测试程序也能正常查询到数据
我们课程上只演示这一个查询的注解开发,其他的同学们下来可以自己实现,都是比较简单。
==注意:==在官方文档中 入门
中有这样的一段话:
所以,注解完成简单功能,配置文件完成复杂功能。
而我们之前写的动态 SQL 就是复杂的功能,如果用注解使用的话,就需要使用到 Mybatis 提供的SQL构建器来完成,而对应的代码如下:
上述代码将java代码和SQL语句融到了一块,使得代码的可读性大幅度降低。
Maven
Mybatis学习笔记-狂神版
简介
Maven本质上是一个项目管理工具,将项目开发和管理过程抽象成一个项目对象模型
POM:项目对象模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DBTTm70Z-1648965526166)(F:JavaEEnoteimages1a4cb448c6ef96c572a673c43f5e711f285ad99436e039f9a47745e1bfc1e9e1.png)]
作用
- 提供一套标准化的项目结构
- 提供了一套标准化的构建流程
- 提供了一套依赖管理机制
基本概念
仓库
仓库:用于存储资源,包含各种jar包
仓库分类:
- 本地仓库:自己电脑上存储的仓库
- 远程仓库:非本机电脑上的仓库
- 中央仓库:Maven团队维护的仓库
- 私服:公司范围内存储资源的仓库
- 私服的作用:
- 保存具有版权的资源
- 一定范围内共享资源
- 私服的作用:
中央仓库
jar包查找顺序:本地仓库→ 远程仓库→ 中央仓库
坐标
坐标是资源的唯一标识
使用坐标来定义项目或引入项目中需要的依赖
Maven坐标组成
- groupId: 定义当前项目隶属组织名称
- artifactId: 定义当前项目名称
- version: 定义当前项目版本号
查找坐标代码片段
配置本地仓库
conf/settings.xml
<localRepository>D:devapache-maven-3.8.4mvn_resp</localRepository>
配置阿里源
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<blocked></blocked>
</mirror>
IDEA创建Maven项目
1.不使用原型创建
创建空项目,之后配置下SDK
设置配置Maven setting路径
创建新模块
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cYctLFiU-1648965526169)(https://s2.loli.net/2022/04/03/Z65LVS1OYjWowGk.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8r2qBa52-1648965526169)(https://s2.loli.net/2022/04/03/BqhfNYlszv9myjo.png)]
在pom.xml
按住alt+Ins
添加单元测试依赖
2.原型创建Java项目
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LsOc78g2-1648965526171)(https://s2.loli.net/2022/04/03/STKnOE2ztFgXsyl.png)]
3.原型创建web项目
添加Tomcat插件
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>80</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<!-- 指定pom的模型版本-->
<modelVersion>4.0.0</modelVersion>
<!-- 打包方式-->
<packaging>war</packaging>
<!-- 组织id-->
<groupId>com.itheima</groupId>
<!-- 项目id-->
<artifactId>java03</artifactId>
<!-- 版本号-->
<version>1.0-SNAPSHOT</version>
<name>java03</name>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>80</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
依赖管理
-
依赖配置
-
依赖是指当前项目运行所需的jar,一个项目可以设置多个依赖
<!-- 设置当前项目所依赖的所有jar--> <dependencies> <!-- 设置具体的依赖--> <dependency> <!-- 依赖所属的群组id--> <groupId>junit</groupId> <!-- 依赖所属项目id--> <artifactId>junit</artifactId> <!-- 依赖版本号--> <version>4.13.2</version> <!-- 只在开发阶段生效--> <scope>test</scope> </dependency> </dependencies>
-
-
依赖传递
- 依赖具有传递性
- 直接依赖:在当前项目中通过依赖配置建立的依赖关系
- 简介依赖:被依赖的资源如果依赖其他资源,当前项目简介依赖其他资源
- 解决传递冲突的问题
- 路径优先:当依赖中出现相同的资源,层级越深,优先级越低,层次越浅,优先级越高
- 声明优先:当资源在相同层级被依赖时,配置顺序靠前的覆盖配置顺序靠后的
- 特殊优先:当同级配置了相同资源的不同版本,后配置的覆盖先配置的
- 依赖具有传递性
-
可选依赖
-
可选依赖是指对外隐藏当前所依赖的资源 — 不透明
<optional>true</optional>
-
<dependency> <!-- 依赖所属的群组id--> <groupId>junit</groupId> <!-- 依赖所属项目id--> <artifactId>junit</artifactId> <!-- 依赖版本号--> <version>4.13.2</version> <optional>true</optional> <!-- 只在开发阶段生效--> <scope>test</scope> </dependency>
-
-
排除依赖
-
主动断开依赖的资源,被排除的资源无需指定版本
<dependency> <!-- 组织id--> <groupId>com.itheima</groupId> <!-- 项目id--> <artifactId>java03</artifactId> <!-- 版本号--> <version>1.0-SNAPSHOT</version> <exclusions> <exclusion> <!-- 依赖所属的群组id--> <groupId>junit</groupId> <!-- 依赖所属项目id--> <artifactId>junit</artifactId> </exclusion> </exclusions> </dependency>
-
-
依赖范围
- 依赖的jar默认情况在任何地方使用,可以通过scope标签设定其作用范围
- 作用范围
- 主程序范围有效(main文件夹范围内)
- 测试程序范围有效
- 是否参与打包(package指令范围内)
scope 主代码 测试代码 打包 范例 compile(默认) Y Y Y log4j test Y junit provided Y Y servlet-api runtime Y jdbc - 依赖范围传递性
- 带有依赖范围的资源在进行传递时,作用范围会受到影响
complle | test | provided | runtime | 直接依赖 | |
---|---|---|---|---|---|
compile | compile | test | provided | runtime | |
test | |||||
provided | |||||
runtime | runtime | test | provided | runtime | |
间接依赖 |
生命周期与插件
Maven构建生命周期描述的是一次构建过程经历了多少个事件
- clear 清理工作
- default 核心工作,编译测试打包部署等
- site 产生报告,发布站点
生命周期与插件
- 插件与生命周期内的阶段绑定,在执行到对应生命周期时执行对应的插件功能
- 默认maven 在各个生命周期上绑定有预设的功能
- 通过插件可以自定义其他功能
Maven继承关系
将dependencies
全部放入dependencyManagement
节点,这样父项目就完全作为依赖统一管理。
<dependencyManagement>
<dependencies>
</dependencies>
</dependencyManagement>
Maven常用命令
clean
命令,执行后会清理整个target
文件夹,在之后编写Springboot项目时可以解决一些缓存没更新的问题。validate
命令可以验证项目的可用性。compile
命令可以将项目编译为.class文件。install
命令可以将当前项目安装到本地仓库,以供其他项目导入作为依赖使用verify
命令可以按顺序执行每个默认生命周期阶段(validate
,compile
,package
等)
Maven测试项目
通过使用test
命令,可以一键测试所有位于test目录下的测试案例,请注意有以下要求:
- 测试类的名称必须是以
Test
结尾,比如MainTest
- 测试方法上必须标注
@Test
注解,实测@RepeatedTest
无效
Maven测试项目
通过使用test
命令,可以一键测试所有位于test目录下的测试案例,请注意有以下要求:
- 测试类的名称必须是以
Test
结尾,比如MainTest
- 测试方法上必须标注
@Test
注解,实测@RepeatedTest
无效
这是由于JUnit5比较新,我们需要重新配置插件升级到高版本,才能完美的兼容Junit5:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<!-- JUnit 5 requires Surefire version 2.22.0 or higher -->
<version>2.22.0</version>
</plugin>
</plugins>
</build>
现在@RepeatedTest
、@BeforeAll
也能使用了。
Junit5
https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
-
常用注解
@Test:标识只是一个测试用例
@BeforeEach:一次性开启所有测试案例每个案例开始之前都会执行一次
@AfterEach:在每个测试方法执行之后,总会调用这个方法,一般用于释放资源
@Disabled:忽略测试用例,让相应的测试用例不运行,用在方法上或者类上
@BeforeAll 和@AfterAll : 和上面的@BeforeEach和@AfterEach非常类似,区别在于,这两个方法必须标注在静态方法上面,且只会执行一次 -
高级注解
@Nested:内嵌测试注解,用于把一组测试归纳起来;
@RepeatedTest:重复多次测试注解
@ParameterizedTest:带参数的注解 -
断言Assertions
assert关键字:可以用来断定一些简单的逻辑
assertEquals:断言结果相等,如果不等,则不通过,有很多对的重载方法;
assertNotNull:断言不为空,
assertThrows:断言抛出异常
assertTimeout:断言超时,如果方法运行的时间超过了指定的时间,就无法通过
assertAll:进行一组断言,如果前一个失败了,后续不再执行。
- 假设Assumptions
Assumptions用来做条件测试的,都在org.junit.jupiter.api.Assumptions包下面,主要有以下几个方法:
assumeTrue:假设某个事情是正确的,[返回某个字符串]
assumeFalse:假设某个情况是错误的,[返回某个字符串]
assumingThat:假设某个表达式是正确时候,执行某个操作
JUL日志系统
JUL日志讲解
日志分为7个级别,详细信息我们可以在Level类中查看:
- SEVERE(最高值)- 一般用于代表严重错误
- WARNING - 一般用于表示某些警告,但是不足以判断为错误
- INFO (默认级别) - 常规消息
- CONFIG
- FINE
- FINER
- FINEST(最低值)
我们之前通过info
方法直接输出的结果就是使用的默认级别的日志,我们可以通过log
方法来设定该条日志的输出级别:
public static void main(String[] args) {
Logger logger = Logger.getLogger(Main.class.getName());
logger.log(Level.SEVERE, "严重的错误", new IOException("我就是错误"));
logger.log(Level.WARNING, "警告的内容");
logger.log(Level.INFO, "普通的信息");
logger.log(Level.CONFIG, "级别低于普通信息");
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DD9aIBkF-1648965526174)(F:JavaEEnoteJavaEEJavaWeb整合JavaWeb整合.assetsimage-20220331091949005.png)]
我们发现,级别低于默认级别的日志信息,无法输出到控制台,我们可以通过设置来修改日志的打印级别:
public static void main(String[] args) {
Logger logger = Logger.getLogger(Main.class.getName());
//修改日志级别
logger.setLevel(Level.CONFIG);
//不使用父日志处理器
logger.setUseParentHandlers(false);
//使用自定义日志处理器
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.CONFIG);
logger.addHandler(handler);
logger.log(Level.SEVERE, "严重的错误", new IOException("我就是错误"));
logger.log(Level.WARNING, "警告的内容");
logger.log(Level.INFO, "普通的信息");
logger.log(Level.CONFIG, "级别低于普通信息");
}
每个Logger
都有一个父日志打印器,我们可以通过getParent()
来获取:
public static void main(String[] args) throws IOException {
Logger logger = Logger.getLogger(Main.class.getName());
System.out.println(logger.getParent().getClass());
}
我们发现,得到的是java.util.logging.LogManager$RootLogger
这个类,它默认使用的是ConsoleHandler,且日志级别为INFO,由于每一个日志打印器都会直接使用父类的处理器,因此我们之前需要关闭父类然后使用我们自己的处理器。
我们通过使用自己日志处理器来自定义级别的信息打印到控制台,当然,日志处理器不仅仅只有控制台打印,我们也可以使用文件处理器来处理日志信息,我们继续添加一个处理器:
//添加输出到本地文件
FileHandler fileHandler = new FileHandler("test.log");
fileHandler.setLevel(Level.WARNING);
logger.addHandler(fileHandler);
注意,这个时候就有两个日志处理器了,因此控制台和文件的都会生效。如果日志的打印格式我们不喜欢,我们还可以自定义打印格式,比如我们控制台处理器就默认使用的是SimpleFormatter
,而文件处理器则是使用的XMLFormatter
,我们可以自定义:
//使用自定义日志处理器(控制台)
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.CONFIG);
handler.setFormatter(new XMLFormatter());
logger.addHandler(handler);
我们可以直接配置为想要的打印格式,如果这些格式还不能满足你,那么我们也可以自行实现:
public static void main(String[] args) throws IOException {
Logger logger = Logger.getLogger(Main.class.getName());
logger.setUseParentHandlers(false);
//为了让颜色变回普通的颜色,通过代码块在初始化时将输出流设定为System.out
ConsoleHandler handler = new ConsoleHandler(){{
setOutputStream(System.out);
}};
//创建匿名内部类实现自定义的格式
handler.setFormatter(new Formatter() {
@Override
public String format(LogRecord record) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
String time = format.format(new Date(record.getMillis())); //格式化日志时间
String level = record.getLevel().getName(); // 获取日志级别名称
// String level = record.getLevel().getLocalizedName(); // 获取本地化名称(语言跟随系统)
String thread = String.format("%10s", Thread.currentThread().getName()); //线程名称(做了格式化处理,留出10格空间)
long threadID = record.getThreadID(); //线程ID
String className = String.format("%-20s", record.getSourceClassName()); //发送日志的类名
String msg = record.getMessage(); //日志消息
// 33[33m作为颜色代码,30~37都有对应的颜色,38是没有颜色,IDEA能显示,但是某些地方可能不支持
return "