🔥个人主页: 中草药
🔥专栏:【Java】登神长阶 史诗般的Java成神之路
🦄一、持久层框架
概念
持久层框架是一种软件工具,它处于应用程序和数据库之间,主要用于处理数据的持久化操作。简单来说,就是将应用程序中的数据以一种持久的方式存储到数据库中,并且在需要的时候能够从数据库中检索出这些数据来供应用程序使用。
例如,在一个电商系统中,用户的信息(如姓名、地址、购买记录等)需要被保存到数据库中,持久层框架就负责将这些用户数据高效、准确地存储起来,并且当用户登录或者查看自己的订单时,又能从数据库中获取这些数据。
作用
(一)数据持久化
1、对象关系映射(ORM)
**持久层框架可以将面向对象编程语言中的对象和关系型数据库中的表进行映射**。例如,在 Java 中一个
User
类,可能有
id
、
name
、
age
等属性,通过持久层框架(如 Hibernate)可以将
User
类自动映射到数据库中的
user
表,其中
User
类的属性对应
user
表中的列。这样,当创建一个
User
对象并将其保存时,框架会自动将对象的属性值插入到对应的数据库表列中。
由于本文主要讲述mybatis的使用,因此也会在一些地方尽可能补充Hibernate的相关使用
**它还能够处理复杂的对象关系,比如一对多、多对多关系**。以一个博客系统为例,一篇博客文章(
Article
)可以有多个评论(
Comment
),这是一对多关系。持久层框架可以帮助正确地在数据库中存储和检索这种关系的数据,例如通过在
Article
表和
Comment
表之间设置外键关联,并且在框架层面进行关联数据的操作。
2、数据存储与检索
**提供了方便的方法来执行 SQL 操作**,对传统的JDBC,进行封装,方便使用,如插入(
INSERT
)、更新(
UPDATE
)、删除(
DELETE
)和查询(
SELECT
)等。例如,MyBatis 框架允许开发者在 XML 映射文件或者通过注解来编写 SQL 语句,然后通过简单的接口方法调用就可以执行这些 SQL 操作。
对于查询操作,能够将查询结果转换为应用程序可以直接使用的对象。例如,执行一个查询用户列表的 SQL 语句后,框架会将数据库返回的结果集自动封装成
User
对象列表返回给业务逻辑层。
(二)数据库访问抽象
1、隔离数据库差异
不同的数据库(如 MySQL、Oracle、SQL Server 等)有不同的 SQL 语法和特性。**持久层框架可以对这些差异进行抽象,使得应用程序代码在更换数据库时不需要大量修改**。例如,Hibernate 提供了统一的 HQL(Hibernate Query Language),它类似于 SQL 但更面向对象,通过 Hibernate 的内部机制可以将 HQL 转换为适用于不同数据库的原生 SQL。
当从 MySQL 数据库切换到 Oracle 数据库时,如果应用程序是基于一个良好的持久层框架构建的,只需要对数据库连接配置等少量部分进行修改,而不需要重新编写大量的数据库访问代码。
2、提供统一的 API
为开发者提供了一套简洁、统一的编程接口来访问数据库。以 MyBatis 为例,开发者通过定义 Mapper 接口,在接口中声明数据库操作方法,如
List<User> getAllUsers();
,然后通过框架的配置和实现,就可以在应用程序中方便地调用这个方法来获取所有用户列表。这种统一的 API 使得代码更加清晰、易读,也便于维护和测试。
(三)性能优化
缓存机制
许多持久层框架都有缓存功能。例如,二级缓存是 MyBatis 中的一个性能优化特性,它可以在 Mapper 级别缓存查询结果。当在一个短时间内多次查询相同的数据时,框架可以直接从缓存中获取数据,而不需要再次向数据库发送查询请求,从而大大提高了数据访问的速度。
缓存的有效利用可以减少数据库的负载,特别是在高并发的应用场景下,如一个热门的电商网站,商品信息的频繁查询可以通过缓存来减少数据库的压力,提高系统的整体性能。
缓存优化机制的出现,同时也会带来一些问题,比如数据的及时更新等等,这就产生了一系列对应的操作,需要更深入地探索
批量操作优化
持久层框架可以优化批量的数据插入、更新和删除操作。比如,在将大量用户数据插入数据库时,框架可以通过拼接 SQL 语句或者使用数据库的批量操作特性(如 JDBC 的
addBatch()
方法)来减少数据库交互次数。以插入 1000 个用户数据为例,通过批量操作可以将 1000 次的插入操作优化为一次或几次(取决于数据库和框架的实现)批量插入操作,从而显著提高性能。
🦓二、MyBatis 基础概述
MyBatis 是一个支持自定义 SQL、存储过程以及高级映射的持久层框架。它在 Java 应用程序和各种数据库之间架起了一座桥梁,使得开发者能够方便地将数据持久化到数据库中,并从数据库中检索数据。与传统的 JDBC 相比,MyBatis 大大简化了数据库操作的复杂性,同时又不失灵活性,让开发者能够更好地掌控 SQL 语句,以满足各种复杂的业务需求。
Maven
配置数据库
spring:
application:
name: Test-Book-Management
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root
password: root
mybatis:
configuration:
#日志配置,数据库配置
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#驼峰自动转化
map-underscore-to-camel-case: true
#xml文件所在位置
mapper-locations: classpath:mapper/**Mapper.xml
当配置了打印日志,可以直接从console看到自己的sql语句
①: 查询语句 ②: 传递参数及类型 ③: SQL执行结果
🐗三、MyBatis的注解使用
**
1、@Select
注解**
功能:用于定义查询语句。它将一个 SQL 查询语句与一个方法关联起来,当调用该方法时,MyBatis 会执行对应的查询语句并返回结果。
示例:假设我们有一个
UserMapper
接口,想要查询所有用户信息。首先创建一个
User
实体类,包含
id
、
username
、
password
等属性。
public interface UserMapper {
@Select("SELECT * FROM users")
List<User> getAllUsers();
}
在这个示例中,
@Select
注解中的 SQL 语句
SELECT * FROM users
表示从
users
表中查询所有记录。当调用
getAllUsers
方法时,MyBatis 会执行这个查询语句,并将结果(每一条记录对应一个
User
对象)封装成一个
List<User>
返回。
**
2、@Update
注解**
功能:用于定义更新语句。通常用于更新数据库表中的记录,如修改用户信息等操作。
示例:
假设我们要更新用户的密码。
public interface UserMapper {
@Update("UPDATE users SET password = #{newPassword} WHERE id = #{id}")
int updateUserPassword(@Param("id") int id, @Param("newPassword") String newPassword);
}
这里的
@Update
注解中的 SQL 语句用于更新
users
表中的记录。
#{id}
和
#{newPassword}
是参数占位符,
@Param
注解用于给参数命名,使得在 SQL 语句中可以正确引用参数。方法的返回值
int
通常表示受更新语句影响的行数。
**
3、@Delete
注解**
功能:用于定义删除语句,用于从数据库表中删除记录。
示例:
假设我们要删除指定 ID 的用户。
public interface UserMapper {
@Delete("DELETE FROM users WHERE id = #{id}")
int deleteUserById(int id);
}
当调用
deleteUserById
方法时,
@Delete
注解中的 SQL 语句会根据传入的
id
参数删除
users
表中对应的记录。方法返回值表示受删除操作影响的行数。
**
4、@Insert
注解**
功能:用于定义插入语句,将数据插入到数据库表中。
示例:假设我们要插入一个新用户。
public interface UserMapper {
@Insert("INSERT INTO users (username, password) VALUES (#{username}, #{password})")
int insertUser(String username, String password);
}
这里的
@Insert
注解中的 SQL 语句用于向
users
表中插入一条新记录。通过
#{username}
和
#{password}
占位符传入要插入的用户姓名和密码。方法返回值表示受插入操作影响的行数,通常成功插入一条记录返回值为 1。
🦣四、MyBatis XML配置文件
首先先与之前一样在Mapper层添加接口
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserInfoXMlMapper {
List<UserInfo> queryAllUser();
}
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.example.demo.mapper.UserInfoMapper">
<!--需要修改的是上面这一行内容-->
</mapper>
创建xml 文件的路径参加yml中的配置
查询操作的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.example.demo.mapper.UserInfoXMlMapper">
<select id="queryAllUser" resultType="com.example.demo.model.UserInfo">
select username,`password`, age, gender, phone from user_info
</select>
</mapper>
- <mapper> 标签:需要指定 namespace 属性,表示命名空间,值为 mapper 接口的全限定 名,包括全包名.类名
- <select> 查询标签:是用来执行数据库的查询操作的: id :是和 Interface (接口)中定义的方法名称⼀样的,表⽰对接口的具体实现方法。 resultType :是返回的数据类型,也就是开头我们定义的实体类.
增
<insert id="insertUser">
insert into userinfo (username, `password`, age, gender, phone) values (#
{username}, #{password}, #{age},#{gender},#{phone})
</insert>
删
<delete id="deleteUser">
delete from user_info where id = #{id}
</delete>
改
<update id="updateUser">
update user_info set username=#{username} where id=#{id}
</update>
若未配置自动驼峰转换,也可以用这种方式
<?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="org.example.mapper.ArticleMapper">
<resultMap id="BaseMap" type="org.example.model.ArticleInfo">
<result column="delete_flag" property="deleteFlag"></result>
<result column="create_time" property="createTime"></result>
<result column="update_time" property="updateTime"></result>
</resultMap>
<select id="list2" resultMap="BaseMap">
select id,title,content,uid,delete_flag,create_time,update_time
from article_info
</select>
</mapper>
🦇五、#{} 和 ${}
**
1、#{}(预编译参数占位符)
**
功能与原理
#{ }
是 MyBatis 中的预编译参数占位符。当 MyBatis 处理 SQL 语句中的
#{ }
时,它会将 SQL 语句发送给数据库进行预编译,把实际的参数值通过安全的方式设置到预编译语句中。这种方式可以有效地防止 SQL 注入攻击。
观察我们的打印日志
最后我们输出的sql语句是这个
select username, `password`, age, gender, phone from user_info where id= ?
我们输入的参数并没有直接进行拼接,是用 ?进行占位,这样的sql我们称为预编译sql
使用场景
几乎适用于所有的 SQL 参数传递场景,尤其是在包含用户输入或者外部传入参数的情况下。例如,在查询、插入、更新和删除操作中作为参数占位符。
2.、**
${}(字符串替换占位符)
**
功能与原理
${ }
是简单的字符串替换占位符。MyBatis 在处理 SQL 语句时,会直接将
${ }
中的表达式替换为实际的值,然后将替换后的 SQL 语句发送给数据库执行。这种方式不会进行预编译,因此在使用时需要特别注意 SQL 注入的风险。
例如,有一个根据表名动态查询的 SQL 语句:
SELECT * FROM ${tableName}
。如果
tableName
的值为
users
,MyBatis 会直接将 SQL 语句替换为
SELECT * FROM users
然后执行。
可以看到, 这次的参数依然是直接拼接在SQL语句中了, 但是字符串作为参数时, 需要添加引号 '' , 使用 ${} 不会拼接引号 '' , 导致程序报错
使用场景
主要用于在 SQL 语句中需要动态替换非参数部分的场景,如动态表名、动态列名等,但要谨慎使用,因为可能会引入 SQL 注入风险。
对比与总结
** 安全性方面**
#{}
是更安全的选择,因为它采用预编译的方式避免了 SQL 注入。而
${}
虽然提供了动态 SQL 构建的灵活性,但在使用外部传入的值时需要谨慎考虑安全性。
** 性能方面**
#{}
的预编译过程可能会有一定的性能开销,但这种开销通常是可以接受的,并且在很多情况下可以提高数据库的执行效率。
${}
由于是简单的字符串替换,没有预编译过程,但可能会因为动态构建 SQL 语句导致数据库缓存机制无法有效利用等问题。总的来说,在大多数情况下应该优先使用
#{}
,只有在确定不会引入 SQL 注入风险的情况下谨慎使用
${}
来满足动态 SQL 的特殊需求。
🦦六、动态sql
传统的 SQL 语句在编写时结构相对固定,如果业务逻辑复杂,涉及到多种条件组合的查询、更新或删除操作,往往需要编写大量冗余且难以维护的 SQL 代码。而 MyBatis 的动态 SQL 则通过一系列的标签和语法,在 XML 映射文件中根据条件动态地拼接 SQL 片段,从而生成符合特定需求的完整 SQL 语句。
** <if>标签**
更多的用于判断,是否有该字段的注入
<insert id="insertUserByCondition">
INSERT INTO userinfo (
username,
`password`,
age,
<if test="gender != null">
gender,
</if>
phone)
VALUES (
#{username},
#{age},
<if test="gender != null">
#{gender},
</if>
#{phone})
</insert>
<trim>标签
<trim>标签用于去除 SQL 语句中多余的前缀或后缀字符,常与其他动态标签结合使用,以确保生成的 SQL 语句格式正确。
标签中有如下属性:
prefix:表示整个语句块,以prefix的值作为前缀
suffix:表示整个语句块,以suffix的值作为后缀
prefixOverrides:表示整个语句块要去除掉的前缀
suffixOverrides:表示整个语句块要去除掉的后缀
<insert id="insertUserByCondition">
INSERT INTO userinfo
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username !=null">
username,
</if>
<if test="password !=null">
`password`,
</if>
<if test="age != null">
age,
</if>
<if test="gender != null">
gender,
</if>
<if test="phone != null">
phone,
</if>
</trim>
VALUES
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username !=null">
#{username},
</if>
<if test="password !=null">
#{password},
</if>
<if test="age != null">
#{age},
</if>
<if test="gender != null">
#{gender},
</if>
<if test="phone != null">
#{phone}
</if>
</trim>
</insert>
<where>标签
where
标签的主要作用是自动处理
WHERE
子句中的条件拼接。
<select id="getUsersByCondition" resultType="com.example.User">
SELECT * FROM users
<where>
<if test="userName!= null">
AND username LIKE #{userName}
</if>
<if test="age!= null">
AND age > #{age}
</if>
</where>
</select>
<set>标签
根据传入的用户对象属性来更新用户数据,可以使用标签来指定动态内容.
<update id="updateUser" parameterType="com.example.User">
UPDATE users
<set>
<if test="userName!= null">
username = #{userName},
</if>
<if test="password!= null">
password = #{password},
</if>
</set>
WHERE id = #{id}
</update>
<foreach>标签
对集合进行遍历时可以使用该标签
collection:绑定方法参数中的集合,如 List,Set,Map或数组对象
item:遍历时的每⼀个对象
open:语句块开头的字符串
close:语句块结束的字符串
separator:每次遍历之间间隔的字符串
<select id="getUsersByIds" resultType="com.example.User">
SELECT * FROM users
WHERE id IN
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
<include>标签
在 MyBatis 的动态 SQL 中,
include
标签是一个用于复用 SQL 片段的强大工具。它允许开发者将一些常用的 SQL 片段抽取出来,在多个 SQL 语句中重复使用,从而提高代码的复用性和可维护性。
定义sql片段
<sql id="userBaseColumns">
id, name, age
</sql>
引用sql片段
<select id="getAllUsersBaseInfo" resultType="com.example.User">
SELECT <include refid="userBaseColumns"/> FROM users
</select>
🦙七、MyBatis-Plus
MyBatis-Plus 是一款 MyBatis 的增强工具,在 MyBatis 的基础上提供了许多实用的功能,旨在简化开发过程,提高开发效率,同时保持 MyBatis 的灵活性和强大的 SQL 定制能力。它通过代码生成器、通用 CRUD 操作、条件构造器、分页插件等一系列特性,让开发者能够更专注于业务逻辑的实现,而减少在基础数据库操作代码编写上的时间和精力消耗。
官方文档:MyBatis-Plus 🚀 为简化开发而生
BaseMapper
MyBatis-Plus 为每个实体类对应的 Mapper 接口提供了通用的增删改查(CRUD)方法,无需开发者手动编写 SQL 语句。这些方法基于 MyBatis 的动态代理机制实现,能够根据方法名自动解析并执行对应的 SQL 操作。
测试
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("xiaocui");
userInfo.setPassword("123456");
userInfo.setAge(18);
userInfo.setGender(1);
userInfoMapper.insert(userInfo);
}
@Test
void update() {
UserInfo userInfo = new UserInfo();
userInfo.setId(11);
userInfo.setUsername("cui");
userInfoMapper.updateById(userInfo);
}
@Test
void delete() {
UserInfo userInfo = new UserInfo();
userInfo.setId(11);
userInfoMapper.deleteById(userInfo);
}
@Test
void select() {
UserInfo userInfo = userInfoMapper.selectById(8);
System.out.println(userInfo);
}
}
**部分常用注解 **
@TableName
可以通过这个注解去标识实体类对应的表
@TableField
绑定字段名
**@TableId **
标识主键
@Data
@TableName("user_info")
public class Userinfo {
@TableId("id")
private Integer id;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
@TableField("delete_flag")
private Integer deleteflag;
private Date createTime;
private Date updateTime;
}
条件构造器
条件构造器是 MyBatis-Plus 的一大亮点,它允许开发者通过编程方式灵活地构建复杂的查询条件,而无需编写大量的 SQL 语句。
以下是主要的 Wrapper 类及其功能:
AbstractWrapper:这是一个抽象基类, 提供了所有 Wrapper 类共有的方法和属性
QueryWrapper:用于构造查询条件, 在AbstractWrapper的基础上拓展了⼀个select方法, 允许指定查询字段.
UpdateWrapper: 用于构造更新条件, 可以在更新数据时指定条件.
LambdaQueryWrapper:基于 Lambda 表达式的查询条件构造器, 它通过 Lambda 表达式来引用实体类的属性,从而避免了硬编码字段名.
LambdaUpdateWrapper: 基于 Lambda 表达式的更新条件构造器, 它允许你使用Lambda 表达式来指定更新字段和条件,同样避免了硬编码字段名的问题.
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void testQuery() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.select("id","username","password","age","gender")
.eq("age",17)
.like("username","wang");
userInfoMapper.selectList(queryWrapper).forEach(System.out::println);
}
@Test
void testQuery1() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.lt("age",15);
UserInfo userInfo = new UserInfo();
userInfo.setUsername("abcd");
userInfoMapper.update(userInfo,queryWrapper);
}
@Test
void testUpdate1() {
UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.set("username","new")
.lt("age",15);
userInfoMapper.update(updateWrapper);
}
@Test
void testQuery2() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("age",12);
userInfoMapper.delete(queryWrapper);
}
@Test
void testUpdate2() {
UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.set("age",21)
.in("id", List.of(6,7));
userInfoMapper.update(updateWrapper);
}
@Test
void testUpdate3() {
UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.setSql("age=age+10")
.in("id", List.of(1,2,3));
userInfoMapper.update(updateWrapper);
}
@Test
void testLambda(){
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.select(UserInfo::getId,UserInfo::getAge)
.eq(UserInfo::getAge,21)
.like(UserInfo::getUsername,"wang");
userInfoMapper.selectList(queryWrapper).forEach(System.out::println);
}
@Test
void testLambda2() {
UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.lambda()
.set(UserInfo::getDeleteFlag,5)
.eq(UserInfo::getAge,25);
userInfoMapper.update(updateWrapper);
}
@Test
void testCustom(){
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username","wang2");
userInfoMapper.selectByCustom(queryWrapper).forEach(System.out::println);
}
@Test
void testCustom2(){
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username","wang3");
userInfoMapper.selectByCustom2(queryWrapper).forEach(System.out::println);
}
@Test
void testCustom3(){
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.in("id",List.of(5,6,7));
userInfoMapper.updateByCustom(10,queryWrapper);
}
}
条件构造器的功能是十分强大的,我们这里只做基础认识,更多繁复的操作,去看官方文档
MyBatis - Plus 的条件构造器提供了丰富的方法来构建各种复杂的数据库查询和更新条件。通过灵活运用这些方法,开发者可以在不编写大量 SQL 语句的情况下,高效地实现复杂的数据库操作。无论是简单的单条件查询,还是涉及多个条件组合、排序、分组和子查询的复杂操作,条件构造器都能满足需求,极大地提高了开发效率和代码的可读性。同时,它还与 MyBatis - Plus 的其他功能(如通用 CRUD 操作、分页插件等)紧密结合,为开发者提供了一个完整、便捷的数据库操作解决方案。
梦想家命长,实干家寿短。——约.奥赖利
🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀
以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐
制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸
版权归原作者 中草药z 所有, 如有侵权,请联系我们删除。