当前位置: 首页 > news >正文

MyBatis 操作数据库

目录

一、什么是MyBatis?

二、MyBatis的使用

2.1 添加依赖

2.2 配置数据库信息

2.2.1 配置数据库连接字符串

2.2.2 配置打印日志

2.2.3 配置日志文件分割

2.2.4 开启驼峰命名

2.2.5 配置 xml 的文件路径

2.3 写持久层代码

传递参数 

传递对象

2.3.1 注解方式

2.3.1.1 增(Insert)

2.3.1.2 删(Delect)

2.3.1.3 改(Update)

2.3.1.4 查(Select)

2.3.2 XML方式

2.3.2.1 增(Insert)

2.3.2.2 删(Delete)

2.3.2.3 改(Update)

2.3.2.4 查(Select)

2.4 单元测试

三、多表查询

四、排序

五、like查询(模糊查询)

六、动态SQL

6.1 XML方式

6.1.1 标签

6.1.2 标签

6.1.3 标签

6.1.4 标签

6.1.5 标签

6.1.6 标签

6.2 注解方式

6.2.1 标签

6.2.2 标签

6.2.3 标签

6.2.4 标签

6.2.5 标签

6.2.6 标签

七、数据库连接池


先来回顾一下 JDBC 的操作流程:

  1. 创建数据库连接池 DataSource
  2. 通过 DataSource 获取数据库连接 Connection
  3. 编写要执行带 ? 占位符的 SQL 语句
  4. 通过 Connection 及 SQL 创建操作命令对象 Statement
  5. 替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值
  6. 使用 Statement 执行 SQL 语句
  7. 查询操作:返回结果集 ResultSet,更新操作:返回更新的数量
  8. 处理结果集
  9. 释放资源

对于 JDBC 来说,整个操作非常的繁琐,我们不但要拼接每⼀个参数,而且还要按照模板代码的方式,⼀步步的操作数据库,并且在每次操作完,还要手动关闭连接等,而所有的这些操作步骤都需要在每个方法中重复书写。那有没有⼀种方法,可以更简单、更方便的操作数据库呢?

这就是我们要学习 MyBatis 的真正原因,它可以帮助我们更方便、更快速的操作数据库


一、什么是MyBatis?

  • MyBatis是⼀款优秀的 持久层 框架,用于简化JDBC的开发。
  • MyBatis本是 Apache的⼀个开源项目iBatis,2010年这个项目由apache迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
  • 在上面提到⼀个词“持久层”,指的就是持久化操作的层,通常指数据访问层(dao),是用来操作数据库的。

简单来说 MyBatis 是更简单完成程序和数据库交互的框架,也就是更简单的操作和读取数据库工具接下来,通过一个入门程序,让大家感受一下通过Mybatis如何来操作数据库

Mybatis并不属于Spring,只是Spring将其集成了

二、MyBatis的使用

Mybatis操作数据库的步骤:

  1. 准备工程(创建springboot工程、数据库表准备、实体类)
  2. 引入Mybatis的相关依赖,配置Mybatis(数据库连接信息)
  3. 编写SQL语句(注解/XML)
  4. 测试

2.1 添加依赖

创建springboot工程,并导入 mybatis的起步依赖、mysql的驱动包

Mybatis 是⼀个持久层框架,具体的数据存储和数据操作还是在MySQL中操作的,所以需要添加
MySQL驱动

项目工程创建完成后,自动在pom.xml文件中,导入Mybatis依赖和MySQL驱动依赖

<!--Mybatis 依赖包-->
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version>
</dependency>
<!--mysql驱动包-->
<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope>
</dependency>

2.2 配置数据库信息

2.2.1 配置数据库连接字符串

Mybatis中要连接数据库,需要数据库相关参数配置

  • MySQL驱动类
  • 登录名
  • 密码
  • 数据库连接字符串

application.yml文件配置如下:

# 数据库连接配置
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=falseusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver

注意事项:
如果使用 MySQL 是 5.x 之前的使用的是"com.mysql.jdbc.Driver",如果是大于 5.x 使用的是“com.mysql.cj.jdbc.Driver”。

application.properties文件配置如下:

#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
#连接数据库的⽤⼾名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=root

 可能的报错信息:

2.2.2 配置打印日志

在Mybatis当中我们可以借助日志,查看到sql语句的执行、执行传递的参数以及执行结果

application.yml文件配置如下:

mybatis:configuration: # 配置打印 MyBatis⽇志log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

application.properties文件配置如下

#指定mybatis输出⽇志的位置, 输出控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

解释:

①: 查询语句
②: 传递参数及类型
③: SQL执行结果

2.2.3 配置日志文件分割

application.yml文件配置如下

logging:logback:rollingpolicy:max-file-size: 1KBfile-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i

application.properties文件配置如下

logging.logback.rollingpolicy.max-file-size=1KB
logging.logback.rollingpolicy.file-name-pattern=${LOG_FILE}.%d{yyyy-MM-dd}.%i

2.2.4 开启驼峰命名

通常数据库列使用蛇形命名法进行命名(下划线分割各个单词),而Java 属性⼀般遵循驼峰命名法约定。为了在这两种命令方式之间启用自动映射,需要将 mapUnderscoreToCamelCase 设置为true。

application.yml文件配置如下

mybatis:configuration:map-underscore-to-camel-case: true #配置驼峰⾃动转换

application.properties文件配置如下

mybatis.configuration.map-underscore-to-camel-case=true #配置驼峰⾃动转换

2.2.5 配置 xml 的文件路径

application.yml文件配置如下

# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis:mapper-locations: classpath:mapper/**Mapper.xml

application.properties文件配置如下

mybatis.mapper-locations=classpath:mapper/**Mapper.xml

MyBatis的固定的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>

2.3 写持久层代码

传递参数 

使用 #{} 的方式获取方法中的参数。#{}大括号里面的参数写的是接口方法中的参数

@Select("select username, `password`, age, gender, phone from user_info where id= #{id} ")
UserInfo queryById(Integer id);

注:

  • 如果mapper接口方法形参只有⼀个普通类型的参数,#{…} 里面的属性名可以随便写,如:#{id}、#{value}。如果mapper接口方法形参有多个参数,#{…} 里面的属性名必须和方法的形参相同但顺序可以不相同。
  • 如果mapper接口方法形参有多个参数但是#{…} 里面的属性名和方法的形参不相同,可以对参数重命名。使用@Param注解将mapper接口方法的形参重命名为#{…}里的属性名
@Select("select * from user_info where age= #{age1} and gender = #{gender1}") // 参数重命名推荐写法, 参数和注解名称保持一致
List<UserInfo> selectUserByAgeAndGender(@Param("gender1") Integer gender, @Param("age1") Integer age);
  • mybatis还给了一个写法,#{param1}、#{param2}等等,param1、param2分别表示方法的第一个参数、第二个参数还可以对参数重命名。

传递对象

使用对象的属性名来获取参数

@Insert("insert into user_info (username, `password`, age, gender, phone) values (#{username},#{password},#{age},#{gender},#{phone})")
Integer insert(UserInfo userInfo);

如果传递对象的时候使用了@Param注解,则需要使用对象.属性

#{} 和 ${}区别

1. #{} 和 ${} 有预编译SQL和即时SQL 的区别,#{}性能更高

2. 安全性上:
SQL注入是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加一些SQL关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。

$存在SQL注入问题,但是不一定会发生,使用$一定要考虑是否存在SQL注入引起的问题。

如果发生在用户登录的场景中, 密码输入为  ' or 1='1 ,就可能完成登录(不是一定会发生的场景,需要看登录代码如何写)

3. 用法上区别:使用${}需要用引号包裹
 

#{} 和 ${} 的使用原则

  1. 能使用#{}尽量使用#{}
  2. 不能使用#{}使用${}必须规避掉SQL注入问题

${}的使用

  • 多表查询时只能使用${}
  • 需要拼接的时候都只能使用${}
  • 排序时只能使用${}
  • 数据量过大时,需要分为多个库多个表来查询(分库分表),这个时候也需要使用${}

2.3.1 注解方式

在项目中,创建持久层接口

6 <include>标签

Mybatis的持久层接口规范⼀般都叫 XxxMapper
@Mapper注解:表示是MyBatis中的Mapper接口

  • 程序运行时,框架会自动生成接口的实现类对象(代理对象),并给交Spring的IOC容器管理。
  • @Select注解:代表的就是select查询,也就是注解对应方法的具体实现内容。
2.3.1.1 增(Insert)
// 写到关键词的话加``才能使用
@Insert("insert into user_info (username, `password`, age, gender) values (#{username}, #{password}, #{age}, #{gender})")
Integer insertUser(UserInfo userInfo); // mybatis直接将UserInfo拆开为属性了

Insert 语句默认返回的是受影响的行数

但有些情况下,数据插入之后,还需要有后续的关联操作,需要获取到新插入数据的id
比如订单系统当我们下完订单之后,需要通知物流系统,库存系统,结算系统等,这时候就需要拿到订单ID

如果想要拿到自增id。需要在Mapper接口的方法上添加一个Options的注解。可以通过@options指定返回的什么属性的值。

useGeneratedKeys:这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字、段)。默认值:false。
keyProperty:指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)

@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into user_info (username, `password`, age, gender) values (#{userInfo.username}, #{userInfo.password}, #{userInfo.age}, #{userInfo.gender})")
Integer insertUserByParam(@Param("userInfo") UserInfo userInfo); // 重命名后打userInfo打包了,Mybatis不会拆,需要按上述形式写
2.3.1.2 删(Delect)
@Delete("delete from user_info where id =#{id}")
Integer delete(Integer id);
2.3.1.3 改(Update)
@Update("update user_info set password = #{password} where id =#{id}")
Integer update(Integer id, String password);// 方法名不能相同
// @Update("update user_info set password = #{password} where id =#{id}")
// Integer update(UserInfo userInfo);
2.3.1.4 查(Select)

当自动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略大小写)。如果没找到Java类中相同的属性则查询结果里面会出现null

解决办法:

  1. 起别名
  2. 结果映射
  3. 开启驼峰命名

方法一:起别名

// 方法一
// 将返回的表的属性字段改为和类相同则对象里面可以拿到数据
@Select("select id, username, password, age, gender, phone, delete_flag as deleteFlag, create_time as createTime, update_time as updateTime from user_info")
List<UserInfo> selectAll();

方法二:结果映射

// 方法二
@Results(id = "BaseMap", value = {@Result(column = "delete_flag", property = "deleteFlag"),@Result(column = "create_time", property = "createTime"),@Result(column = "update_time", property = "updateTime"),
})
@Select("select id, username, password, age, gender, phone, delete_flag, create_time, update_time from user_info")
List<UserInfo> selectAll();// 之后就不用重复使用@Results书写,其他的可以复用
@ResultMap("BaseMap")
@Select("select id, username, password, age, gender, phone, delete_flag, create_time, update_time from user_info where id= #{aa}")
UserInfo selectUserById(Integer id);

方法三:开启驼峰命名

通常数据库列使用蛇形命名法进行命名(下划线分割各个单词),而Java 属性⼀般遵循驼峰命名法约定。为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase 设置为 true。

mybatis:configuration:map-underscore-to-camel-case: true #配置驼峰⾃动转换

Java 代码不做任何处理

@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from user_info")
public List<UserInfo> queryAllUser();

2.3.2 XML方式

方法定义 Interface

方法实现:XXXMapper.xml

配置 mybatis xml 的文件路径:

# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis:mapper-locations: classpath:mapper/**Mapper.xml
  • classpath指的是resources这个目录

MyBatis的固定的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>

id后面的名称必须和方法名一样,resultType写为路径+类名(只有select才有resultType):

报错及解决思路:

2.3.2.1 增(Insert)
<insert id="insertUser">insert into userinfo (username, `password`, age, gender, phone) values (#{username}, #{password}, #{age},#{gender},#{phone})
</insert>

使用@Param设置参数名称的话,使用方法和注解类似

UserInfoMapper接口:

Integer insertUser2(@Param("userInfo") UserInfo userInfo);

UserInfoMapper.xml实现:

<insert id="insertUser2">insert into user_info (username, `password`, age, gender, phone) values (#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender},#{userInfo.phone})
</insert>

获得自增id

<insert id="insertUser" useGeneratedKeys="true" keyProperty="id"><include refid="insertCol"></include>values (#{username}, #{password}, #{age}, #{gender})
</insert>
2.3.2.2 删(Delete)
<delete id="deleteUserById">delete from user_info where id = #{id}
</delete>
2.3.2.3 改(Update)
<update id="updateUser">update user_info set password = #{password} where id =#{id}
</update>
2.3.2.4 查(Select)

1. 起别名

<select id="selectAll" resultType="com.bite.mybatis.demo.model.UserInfo">select id, username, password, age, gender, phone,delete_flag as deleteFlag,create_time as createTime,update_time as updateTimefrom user_info
</select>

2. 结果映射

<resultMap id="BaseMap" type="com.bite.mybatis.demo.model.UserInfo"><id property="id" column="id"></id><result column="delete_flag" property="deleteFlag"></result><result property="createTime" column="create_time"></result><result property="updateTime" column="update_time"></result>
</resultMap>
<select id="selectAll" resultMap="BaseMap">select id, username, password, age, gender, phone,delete_flag, create_time, update_timefrom user_info
</select>
  • 一个xml中可以定义多个resultMap
  • <resultMap></resultMap>中id不能重复,type表示的是对应的Java对象
  • <id></id>表示的是主键,也可以不写,只是规范
  • <result></result>表示是普通字段

3. 开启驼峰命名

mybatis:configuration:map-underscore-to-camel-case: true #配置驼峰⾃动转换

2.4 单元测试

@SpringBootTest
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userInfoMapper;@Testvoid queryAllUser() {List<UserInfo> userInfoList = userInfoMapper.queryAllUser();System.out.println(userInfoList);}
}

三、多表查询

企业开发中尽量避免使用多表查询,尤其是C端业务,对性能要求高,对于出错,容忍度低。MySQL都是集群,集群成本高,多个项目一起使用。MySQL有线程池,每一个集群能承担的请求量是确定的,使用慢sql可能会使短时间内其他项目进不来了(影响其他项目使用)。同时从性能来说,mysql集群扩容没有程序扩容简单,程序扩容后可以几台机器同时运行

集群:可以简单地理解为备份。集群分为主从,主用来写(insert delete update)并同步给从,从用来读(select)

四、排序

使用  ${} 可以实现排序查询,而使用 #{} 就不能实现排序查询了。排序只能使用$!!!

@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time from user_info order by id ${sort} ")
List<UserInfo> queryAllUserBySort(String sort);

注意: 此处 sort 参数为String类型,会默认加上单引号,但是SQL语句中,排序规则是不需要加引号 '' 的,所以此时的${sort} 也不加引号

但是使用$又存在SQL注入的问题,解决办法:

1.枚举:只能为desc或者asc

2.参数校验:如果不为desc或者asc就拒接

五、like查询(模糊查询)

like 使用 #{} 报错

把 #{} 改成 ${} 可以正确查出来,但是${}存在SQL注入的问题,所以不能直接使用 ${}。而like查询的内容不能进行校验
解决办法: 使用 mysql 的内置函数 concat() 来处理,实现代码如下:

@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time from user_info where username like concat('%',#{key},'%')")
List<UserInfo> queryAllUserByLike(String key);

从而可以使用#{}

六、动态SQL

6.1 XML方式

6.1.1 <if>标签

<insert id="insertUserByCondition">insert into user_info (username,`password`,age<if test="gender!=null">,gender</if>)values (#{username},#{password},#{age}<if test="gender!=null">,#{gender}</if>)
</insert>
  • test里面的是参数,和#{}中的一样,是方法的参数(类对应的参数,属性的名称),不过不写#{}了而已
  • ,gender 和 ,#{gender}前面的逗号必须放在if中,否则sql语句里面会有多一个逗号而后面没有内容,就报错了

6.1.2 <trim>标签

之前的插入用户功能,只是有一个 gender 字段可能是选填项,如果有多个非必填字段时,使用<if>标签可能会出现问题,此时⼀般考虑使用标签结合标签,对多个字段都采取动态生成的方式。

标签中有如下属性:

  • prefix:表示整个语句块,添加以prefix的值作为前缀
  • suffix:表示整个语句块,添加以suffix的值作为后缀
  • prefixOverrides:表示整个语句块要删除的前缀
  • suffixOverrides:表示整个语句块要删除的后缀
    <insert id="insertUserByCondition">insert into user_info<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></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></trim></insert>

6.1.3 <where>标签

作用:

  • 去除前面多余的and
  • 当有查询条件时,生成where关键字,当没有查询条件时,去除where关键字
    <select id="selectUserByCondition" resultType="com.bite.mybatis.demo.model.UserInfo">select * from user_info<where><if test="age!=null">and age=#{age}</if><if test="deleteFlag!=null">and delete_flag = #{deleteFlag}</if></where></select>

6.1.4 <set>标签

根据传入的用户对象属性来更新用户数据,可以使用标签来指定动态内容。
作用:

  1. 添加set关键字
  2. 去除末尾的逗号
    <update id="updateByCondition">update user_info<trim suffixOverrides=","><if test="username!=null">username = #{username},</if><if test="password!=null">password = #{password},</if><if test="gender!=null">gender = #{gender}</if></trim><where>id =#{id}</where></update>

上面xml和下面的等价:

    <update id="updateByCondition">update user_info<set><if test="username!=null">username = #{username},</if><if test="password!=null">password = #{password},</if><if test="gender!=null">gender = #{gender}</if></set><where>id =#{id}</where></update>

6.1.5 <foreach>标签

对集合进行遍历时可以使用该标签。标签有如下属性:

  • collection:绑定方法参数中的集合,如 List,Set,Map或数组对象
  • item:遍历时的每一个对象
  • open:语句块开头的字符串
  • close:语句块结束的字符串
  • separator:每次遍历之间间隔的字符串
    <delete id="batchDelete">delete from user_info where id in<foreach collection="ids" separator="," item="id" open="(" close=")" >#{id}</foreach></delete>

6.1.6 <include>标签

在xml映射文件中配置的SQL,有时可能会存在很多重复的片段,此时就会存在很多冗余的代码

对重复的代码片段进行抽取,将其通过 <sql> 标签封装到⼀个SQL片段,然后再通过<include> 标签进行引用。

  • <sql> :定义可重用的SQL片段,可以是任意片段,可以是不能执行的片段。一般是定义列名
  • <include> :通过属性refid,指定包含的SQL片段
    <sql id="insertCol">insert into user_info (username, `password`, age, gender)</sql>
    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id"><include refid="insertCol"></include>values (#{username}, #{password}, #{age}, #{gender})</insert>

一般是定义列名:

6.2 注解方式

需要使用<script></script>标签,标签中内容和XML方式相同(直接从xml方式中的内容复制进来就行,)

6.2.1 <if>标签

    @Insert("<script>" +"insert into user_info" +" <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>" +" </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>" +" </trim>"+"</script>")Integer insertUserByCondition(UserInfo userInfo);

6.2.2 <trim>标签

6.2.3 <where>标签

6.2.4 <set>标签

6.2.5 <foreach>标签

6.2.6 <include>标签

七、数据库连接池

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。(池化技术,避免频繁的创建连接,销毁连接)

  • 没有使用数据库连接池的情况: 每次执行SQL语句,要先创建⼀个新的连接对象,然后执行SQL语句,SQL语句执行完,再关闭连接对象释放资源。这种重复的创建连接,销毁连接比较消耗资源。
  • 使用数据库连接池的情况: 程序启动时,会在数据库连接池中创建⼀定数量的Connection对象, 当客户请求数据库连接池,会从数据库连接池中获取Connection对象,然后执行SQL, SQL语句执行完,再把Connection归还给连接池。

优点:

  1. 减少了网络开销
  2. 资源重用
  3. 提升了系统的性能

常见的数据库连接池:

  • C3P0
  • DBCP
  • Druid
  • Hikari(SpringBoot默认使用的数据库连接池)

目前比较流行的是 Hikari,Druid

Hikari 是日语"光"的意思(ひかり),Hikari也是以追求性能极致为目标

如果我们想把默认的数据库连接池切换为Druid数据库连接池,只需要引入相关依赖即可

<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-3-starter</artifactId><version>1.2.21</version>
</dependency>

如果SpringBoot版本为2.X,使用druid-spring-boot-starter 依赖

<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.17</version>
</dependency>

运行结果:

http://www.dtcms.com/a/319901.html

相关文章:

  • Orange的运维学习日记--33.DHCP详解与服务部署
  • Linux 系统启动、systemd target 与 root 密码重置指南
  • vector模拟实现
  • Seelen UI:高效的设计与原型制作工具
  • 解决winform中的listbox实现拖拽时,遇到combox控件会闪烁的问题
  • APM-SigNoz可观测性系统搭建
  • TDengine IDMP 文档介绍
  • 密集场所漏检率↓78%!陌讯自适应多模态口罩识别算法实战解析
  • 【bioinfo】ncbiRefSeq数据库下载
  • 零基础-动手学深度学习-9.1. 门控循环单元(GRU)及代码实现
  • 解决 npm i node-sass@4.12.0 安装失败异常 npm i node-sass异常解决
  • 如何使用 pnpm创建Vue 3 项目
  • 玳瑁的嵌入式日记D14-0807(C语言)
  • 蓝凌EKP产品:列表查询性能优化全角度
  • C++引用专题(上):详解C++传值返回和传引用返回
  • JavaScript核心概念解析:从基础语法到对象应用
  • 部署 AddressSanitizer(ASan)定位内存泄漏、内存越界
  • Java+Vue合力开发固定资产条码管理系统,移动端+后台管理,集成资产录入、条码打印、实时盘点等功能,助力高效管理,附全量源码
  • 【保姆级喂饭教程】python基于mysql-connector-python的数据库操作通用封装类(连接池版)
  • SPI TFT全彩屏幕驱动开发及调试
  • Sentinel原理之责任链详解
  • imx6ull-驱动开发篇12——GPIO子系统驱动LED
  • C++高频知识点(十五)
  • Qwen-Image开源模型实战
  • 【Floyd】Shortest Routes II
  • 显卡服务器的作用主要是什么?-哈尔滨云前沿
  • 使用内网穿透工具1分钟上线本地网站至公网可访问,局域网电脑变为服务器
  • Mysql数据仓库备份脚本
  • 2.7 (拓展)非父子通信(事件总线和provide-inject)详解
  • 2025 年华数杯全国大学生数学建模竞赛B题 网络切片无线资源管理方案设计--完整成品、思路、代码、模型结果分享,仅供学习~