MyBatis
持久层框架
MyBatis 是一个流行的 Java 持久层框架,专注于简化数据库操作。它通过 XML 或注解配置,支持自定义 SQL、存储过程和高级映射,极大地减少了繁琐的 JDBC 代码。MyBatis 的灵活性使其适用于需要精细控制 SQL 性能的场景,同时也能实现 SQL 与 Java 代码的分离,提升代码的可维护性
CRUD步骤
1.添加mybatis依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
2.创建java实体类
@Data
public class Emp {
private Integer id;
private String empName;
private Integer age;
private Double empSalary;
}
3.写Service接口,但此时是Mapper(加@Mapper注解)
@Mapper//告诉Spring,这是MyBatis操作数据库的Mapper接口
public interface EmpMapper {
Emp getEmpById(Integer id);
void addEmp(Emp emp);
void deleteEmpById(Integer id);
void updateEmp(Emp emp);
List<Emp> getAllEmp();
}
4.为接口写xml文档,编写sql语句
5.调用接口方法即可
数据处理
#{}和${}
1. #{}
(PreparedStatement 方式,占位符)
特点
-
采用 预编译 方式,使用 JDBC
PreparedStatement
进行 SQL 传参。 -
防止 SQL 注入,因为参数是作为占位符传递,而不是直接拼接 SQL 语句。
-
性能较优,SQL 解析和执行效率较高。
2. ${}
(字符串替换方式)
特点
-
直接进行 字符串拼接,参数会被原样替换到 SQL 语句中。
扫描二维码关注公众号,回复: 17574729 查看本文章 -
容易引发 SQL 注入,因为参数值直接拼接到 SQL 语句里。
-
主要用于 动态 SQL(表名、列名) 需要动态拼接的场景。
<select id="getUserByColumn" resultType="User">
SELECT * FROM user WHERE ${column} = #{value}
</select>
编译的语句
SELECT * FROM user WHERE username = ?
多个参数时
@param注解可指定参数
`
编写sql语句时,若类的属性名和数据库的字段名不同,会导致数据无法赋值
解决方式
1.让属性名和字段名一样(不推荐)
2.编写sql语句时,给字段名起别名成属性名
3.在配置文件中声明驼峰命名转换mybatis.configuration.map-underscore-to-camel-case=true
4.在Mapperxml文件里写resultMap改名
<resultMap id="EmpRM" type="org.example.mybatis01helloworld.bean.Emp">
<id column="id" property="id" />
<result column="emp_name" property="empName" />
<result column="age" property="age" />
<result column="emp_salary" property="empSalary" />
</resultMap>
最佳实践:
1.开启驼峰命名规则
2.1 搞不定的用自定义映射(resultMap)
关联查询
一对一的映射用association标签
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<association property="address" javaType="Address">
<id property="id" column="address_id"/>
<result property="city" column="city"/>
</association>
</resultMap>
<select id="getUserWithAddress" resultMap="userResultMap">
SELECT u.id, u.name, a.id as address_id, a.city
FROM users u
LEFT JOIN addresses a ON u.address_id = a.id
WHERE u.id = #{id}
</select>
一对多映射使用collection标签
<resultMap id="departmentResultMap" type="Department">
<id property="id" column="id"/>
<result property="name" column="name"/>
<collection property="employees" ofType="Employee">
<id property="id" column="employee_id"/>
<result property="name" column="employee_name"/>
</collection>
</resultMap>
<select id="getDepartmentWithEmployees" resultMap="departmentResultMap">
SELECT d.id, d.name, e.id as employee_id, e.name as employee_name
FROM departments d
LEFT JOIN employees e ON d.id = e.department_id
WHERE d.id = #{id}
</select>
原生分布查询
编写多个sql语句,调用多次方法
自动分布查询
<resultMap id="DepartmentResultMap" type="Department">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!-- 一对多分步查询 -->
<collection property="employees" column="id"#传入的参数 select="getEmployeesByDeptId"#自动执行的sql语句 fetchType="lazy"/>
</resultMap>
<select id="getDepartmentById" resultMap="DepartmentResultMap">
SELECT * FROM department WHERE id = #{id}
</select>
<select id="getEmployeesByDeptId" resultType="Employee">
SELECT * FROM employee WHERE department_id = #{id}
</select>
要点: select属性写sql语句 column传入参数
动态sql
<if test=" ">判断是否执行
<select id="getUsers" resultType="User">
SELECT * FROM user
WHERE 1=1#防止逻辑错误
<if test="name != null">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</select>
where标签 解决where后面无条件或多and
set 标签 解决不传值时多逗号的情况
trim标签 可以代替where 和set 标签
-
prefix:指定需要添加的前缀,例如
WHERE
或SET
。 -
prefixOverrides:指定需要移除的前缀,比如
AND
、OR
。 -
suffix:指定需要添加的后缀。
-
suffixOverrides:指定需要移除的后缀。
choose/when/ortherwise 相当于switch(break)一旦找到配匹配的when就不会继续检查后面的when了
<select id="getUser" resultType="User">
SELECT * FROM user
<where>
<choose>
<when test="id != null">
id = #{id}
</when>
<when test="name != null">
name = #{name}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</where>
</select>
foreach标签 collection 集合名
item 集合中的变量
separator 设置分隔符
open 指定开始的前缀
close 结束后缀
<select id="getUsersByIds" resultType="User">
SELECT * FROM user WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
抽取可复用的sql片段
<sql id = "column_name">
xxxx
</sql>
缓存机制
两级缓存机制:
一级缓存机制
同一个 SqlSession
内 共享缓存,第一次查询数据库并缓存结果,第二次查询相同的sql时不访问数据库,直接从缓存取
二级缓存机制:
手动开启,在同一个mapper内,跨sqlsession共享
查找顺序:二一库
1.事务级别 默认开启
<!---同一个事务期间,前面查询的数据,后面如果要执行相同查询,会从一级缓存中获取数据-->
有时候缓存会失效:缓存不命中
-
查询的东西不一样
-
两次查询之间,进行了增删改(Mybatis会认为数据库发生了改变,所以要再发送一次查询)
2.所有事务共享 :需要手动配置开启
分页插件
1.手写limit语句
SELECT * FROM table_name LIMIT offset, pageSize;
offset
:跳过的记录数((pageNum - 1) * pageSize
)
pageSize
:每页显示的记录数
2.使用PageHelper插件
导入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.2</version>
</dependency>
service层
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo;
public PageInfo<User> getUsersByPage(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize); // 开启分页
List<User> users = userMapper.getAllUsers(); // 查询所有
return new PageInfo<>(users); // 封装分页信息
}
controller层实现
MybatisPlus
适合单表查询,多表查询还是自己编写更好
快速入门
导入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.10.1</version>
</dependency>
在接口继承BaseMaper<T>
注入依赖.就可调用各种单表查询的方法
MybatisPlus通过扫描实体类,基于反射获取实体类信息作为数据库表信息
-
类名驼峰转下划线作为表名
-
名为id的字段作为主键
-
变量名驼峰转下划线作为表的字段名
常用注解
@TableName: 用来指定表名
@TableId: 用来指定表中的主键字段
@TableField: 用来指定表中的普通字段信息
成员变量名和数据库字段名不一致
成员变量名以is开头,且是布尔值
成员变量名和数据库关键字冲突
成员变量不是数据库字段@TableField(exist = false)
常见配置
核心功能
条件构造器Wrapper
基于QueryWrapper的查询
1.查询出名字中带o的,存款大于等于1000元的人的id,username,info,balance字段
@Test
public void testQueryWrapper(){
//构建查询对象
QueryWrapper<User> userQueryWrapper = new QueryWrapper<User>().select("id", "username", "info", "balance")
.like("username", "o")
.gt("balance", 1000);
//查询
List<User> users = userMapper.selectList(userQueryWrapper);
log.info("users = " + users);
users.forEach(System.out::println);
}
2.更新用户名为jack的用户工资 为2k
@Test
public void testUpdateByQueryWrapper(){
User user = new User();
user.setBalance(20000);
QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "jack");
userMapper.update(user,wrapper);
}
基于UpdateWrapper的查询
1.把id为 1,2,4的用户工资减200
@Test
public void testReduceUserBalance(){
List<Long> ids = List.of(1L, 2L, 4L);
UpdateWrapper<User> wrapper = new UpdateWrapper<User>().setSql("balance = balance-200")
.in("id",ids);
userMapper.update(null,wrapper);
}
使用lambdaQueryWrapper和LambdaUpdateWrapper
@Test
public void testQueryWrapper(){
//构建查询对象
LambdaQueryWrapper<User> userQueryWrapper = new LambdaQueryWrapper<User>().select(User::getId,User::getUsername,User::getInfo,User::getBalance)
.like(User::getUsername, "o")
.gt(User::getBalance, 1000);
//查询
List<User> users = userMapper.selectList(userQueryWrapper);
users.forEach(System.out::println);
}
自定义SQL
1.通过注解方式编写自定义sql
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT * FROM user WHERE age > #{age}")
List<User> selectByAge(int age);
}
@Select
注解可以用于执行 查询操作。
你也可以使用 @Insert
、@Update
、@Delete
注解来编写相应的操作 SQL。
利用mp构建复杂的where条件,然后剩下的sql语句自己写
IService接口
创建一个继承自IService的接口
public interface IUserService extends IService<User> {
void deductBalanceById(Long id, Integer money);//可自定义方法
}
创建接口的实现类,还要继承ServiceImpl<UserMapper,User>
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
// 通过继承 ServiceImpl,就不需要显式实现 CRUD 方法
@Override
public User getUserByEmail(String email) {
return baseMapper.selectOne(new QueryWrapper<User>().eq("email", email));
}
}
实际开发中,使用IService接口更常见