访问流程(mybatis)
- 配置信息:在
application.yml
中配置数据库连接信息
# 数据源配置
spring:
datasource:
# your_database(数据库名称)、useUnicode(使用unicode字符集)、characterEncoding(编码格式)、useSSL(使用SSL加密)、serverTimezone(时区)
url: jdbc:mysql://localhost:3306/your_database?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai # 数据库的URL
username: root # 数据库的用户名
password: your_password # 数据库的密码
# MyBatis配置
mybatis:
mapper-locations: classpath:mapper/*.xml # mapper文件位置
type-aliases-package: com.yourpackage.model # 别名包路径
configuration:
#数据库表单命名单词之间以`_`分隔,而属性采用驼峰命名法,需要进行转化
map-underscore-to-camel-case: true # 开启mybatis驼峰命名自动映射
# 配置日志级别,便于调试
logging:
level:
org.mybatis: debug # debug级别可以输出SQL语句,便于调试
- 创建实体类:创建一个与数据库表对应的实体类,使用Java类的属性映射数据库表的字段
@TableName("users") // 使用MyBatis-Plus的@TableName注解指定表名
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id; // 对应数据库表的id字段
private String username; // 对应数据库表的username字段
private String email; // 对应数据库表的email字段
}
- 编写Mapper接口:在repository文件夹下编写
Mapper
接口并定义SQL操作的方法
- 直接使用
@Select
、@Insert
、@Update
或@Delete
等注解来指定SQL语句
@Mapper // 标记MyBatis的映射器接口,创建全局代理实例
@Repository // 标记数据访问层的组件,实现基于接口的依赖注入,并能够自动转换数据库相关的异常
public interface UserMapper {
// 执行sql时,会将`#{...}`替换为`?`,生成预编译sql,会自动设置参数值,性能更高,更安全
@Select("SELECT * FROM users WHERE id = #{id}") // SQL 查询语句,
User getUserById(int id);
@Insert("INSERT INTO users (name, age) VALUES (#{name}, #{age})") // SQL 插入语句
@Options(useGeneratedKeys = true,keyProperty = "id") // 返回生成的主键ID
int insertUser(User user);
}
- 通过XML映射文件来配置SQL语句 (适合需要使用动态SQL的场景)
- 公共属性
id
:指定Mapper接口中的方法parameterType
:指定输入参数的类型,MyBatis 会自动将对象的属性与 SQL 中的占位符对应起来(只能传入一个,如果要传入多个参数,需要在Mapper接口的定义中中指定参数)
<insert>
useGeneratedKeys
:获取由数据库自动生成的主键值keyProperty
:指定Java对象中的哪个属性应该被赋予由数据库生成的主键值keyColumn
:指定数据库中哪个列是生成的主键列,默认使用 keyProperty 属性名对应的列名作为主键列。在某些情况下,数据库的主键列名与对象的属性名称不同(例如,表中列名为 user_id,而 Java 对象的属性名是 id)。此时,keyColumn 用来指定数据库中主键的列名
<select>
resultType
:查询结果应该映射到的Java对象的全限定名resultMap
:引用定义的<resultMap>
元素,用于处理复杂的结果映射fetchSize
:指定数据库驱动程序每次批量返回的结果行数timeout
:设置超时时间,等待数据库返回结果的秒数
<update>
:只有公共属性<delete>
:只有公共属性
- 公共属性
// src/main/.../repository/
@Mapper
@Repository
public interface UserMapper {
public User getUserById(int id); // 单参数类型可以在xml文件中传入
//使用 `@Param` 注解来指定每个参数的名字,以便于在SQL中引用
public int updateClassName(@Param("id") int id, @Param("className") String className); // 多参数类型在接口定义处传入
...
}
// src/main/resources/mappers/
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.example.demo.repository.UserMapper"> <!-- 接口位置 -->
<!-- 定义resultMap ,这里假设数据库列名与实体类属性名不一致-->
<resultMap id="userResultMap" type="User">
<result property="id" column="user_id"/>
<result property="name" column="user_name"/>
</resultMap>
<!-- 使用resultMap ,根据映射关系将 数据库中列的值 传递给 对象中对应属性 -->
<select id="selectUser" resultMap="userResultMap">
SELECT * FROM users WHERE user_id = #{id}
</select>
<!-- 使用resultType ,确保数据库列名与实体类属性名一致,才能将数据库对应列的值传递给对象相对应的属性-->
<select id="getUserById" resultType="com.example.demo.entity.User">
SELECT * FROM users WHERE id = #{id}
</select>
<insert id="insertStudent" parameterType="com.fl.boot.entity.Student" useGeneratedKeys="true" keyProperty="id">
insert into student(id,sname,classId,birthday,email)
values (#{id},#{sname},#{classId},#{birthday},#{email});
</insert>
<delete id="deleteById" parameterType="int">
DELETE FROM students WHERE id = #{id}
</delete>
<update id="updateClassName" >
UPDATE students SET class_name = #{className} WHERE id = #{id};
</update>
</mapper>
- 自动注入Mapper:通过
@Autowired
注解来自动注入Mapper接口并调用它的方法 - 事务管理:使用
@Transactional
注解来管理事务,确保数据库操作的原子性
@Service
@Transactional
public class UserService {
@Autowired
private UserMapper userMapper;
public User getUser(int id) {
return userMapper.getUserById(id);
}
public int addUser(User user) {
return userMapper.insertUser(user);
}
}
细节
数据库连接池
功能
- 应用程序需要与数据库进行交互时,都需要建立一个新的数据库连接,而这种行为是十分耗费资源的
- 而数据库连接池通过复用已建立的数据库连接来减少每次操作时的创建和销毁开销,有效管理连接的生命周期,避免了连接泄露、资源浪费以及数据库过载等问题
实现
- Spingboot会默认使用
HikariCP
作为连接池 - 而我们配置
Druid
作为连接池,因为它更加智能、准确、误报率低- 引入依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.22</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version>
2. 配置数据库信息
yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource # 指定连接池类型
druid:
initialSize: 5 # 初始化时建立物理连接的个数,减少应用程序在运行时获取数据库连接的等待时间
minIdle: 5 # 最小连接池数量
maxActive: 20 # 最大连接池数量
maxWait: 60000 # 获取连接时最大等待时间,单位毫秒
timeBetweenEvictionRunsMillis: 60000 # 检测需要关闭的空闲连接的间隔时间,单位毫秒
minEvictableIdleTimeMillis: 300000 # 一个连接在池中最小生存的时间,单位毫秒
validationQuery: SELECT 1 FROM DUAL # 检测连接是否有效的SQL语句,如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用
testWhileIdle: true # 定期检查池中空闲连接是否有效
testOnBorrow: false # 每次从连接池中借用连接时检测连接是否有效
testOnReturn: false # 每次从连接池中归还连接时检测连接是否有效
poolPreparedStatements: true # 是否缓存preparedStatement
filters: stat,wall,log4j # 配置监控统计拦截的插件,stat:监控统计、log4j:日志记录、wall:防御sql注入
maxPoolPreparedStatementPerConnectionSize: 20 # 每个连接可以创建的最大预编译语句
useGlobalDataSourceStat: true # 开启全局数据源统计功能
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 # 开启SQL合并,设置慢SQL的阈值
mybatis:
configuration:
map-underscore-to-camel-case: true
```
动态SQL
功能
- 通过XML映射文件或注解包含条件判断、循环和表达式计算等逻辑,根据程序运行时的条件,动态构建SQL语句,应对复杂的数据库操作需求
实现
<where>
:用于生成WHERE子句,自动为你添加第一个子句的WHERE
前缀,如果所有条件都不满足,则不会生成WHERE子句,同时还会去除多余的AND
或OR
<if>
:根据条件判断包含SQL片段,使用test属性进行条件判断是否需要拼接SQL<choose>
、<when>
、<otherwise>
:类似于Java中的if-else语句,不能单独使用,适用于需要多个条件选择的情况
<select id="findStudent" resultType="com.fl.boot.entity.Student">
SELECT * FROM students
<where>
<if test="name != null">
name LIKE CONCAT('%',#{name},'%')
</if>
<choose>
<when test="gender != null">
AND gender = #{gender}
</when>
<when test="age != null">
OR age = #{age}
</when>
<when test="className != null ">
class_name = #{className}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</where>
</select>
<foreach>
:用于遍历集合,生成批量SQL语句,如IN查询collection
:遍历的集合item
:遍历出来的元素separator
:分隔符open
:遍历开始前拼接的sql片段close
:遍历结束后的sql片段
public List<Student> findStudentByIdList(@Param("ids") List<Integer> idList);
<!-- 传入一个 `ids` 列表,查询 `students` 表中所有符合条件的记录 -->
<select id="findStudentByIdList" resultType="com.fl.boot.entity.Student">
SELECT * FROM students
WHERE id IN
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
<set>
:用于动态生成UPDATE
语句的SET
子句,只更新改变的字段
<!-- 根据传入id,动态修改对应学生的属性 -->
<update id="updateStudent" parameterType="com.fl.boot.entity.Student">
UPDATE students
<set>
<if test="name != null">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="gender">
gender = #{gender},
</if>
<if test="className">
class_name = #{className},
</if>
</set>
WHERE id = #{id}
</update>
<trim>
:自定义字符串的截取规则prefix
:指定添加到生成的SQL片段之前的前缀suffix
:指定添加到生成的SQL片段之后的后缀preifxOverrides
:去除SQL片段之前的关键字或者字符suffixOverrides
:去除SQL片段之后的关键字或者字符
<!-- 使用 trim 代替 where -->
<select id="findStudent" resultType="com.fl.boot.entity.Student">
SELECT * FROM students
<!-- 如果name==null,则SQL片段会以AND开头,会被`preifxOverrides`去除,并会在片段前添加`WHERE` -->
<trim prefix="WHERE" prefixOverrides="AND |OR">
<if test="name != null">
name LIKE CONCAT('%',#{name},'%')
</if>
<if test="gender != null">
AND gender = #{gender}
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="className != null ">
AND class_name = #{className}
</if>
</trim>
</select>
<sql>
:定义可以在其他SQL元素中重用的SQL片段<include>
:通过属性refid,指定包含的SQL片段
<sql id="selectColumns">
id,name,age,gender,class_name
</sql>
<select id="findGirl" resultType="com.fl.boot.entity.Student">
SELECT <include refid="selectColumns"/>
FROM students
WHERE gender = '女'
</select>