Mybatis

Mybatis框架

框架

框架(Framework)是一种半成品软件,它提供了一套完整的、可重用的设计、代码和库,旨在帮助开发者解决特定领域或类型的问题。框架通常规定了软件系统的整体结构、主要组件及其之间的关系,以及它们之间的交互方式。框架相对于库不仅提供了一组工具,还规定了应用的结构和流程。开发者需要在框架的约束下工作,通过实现特定的接口或继承特定的类来完成应用的功能。

JDBC的问题

JDBC(Java Database Connectivity)是Java语言中用于执行SQL语句的应用程序接口(API),它为Java程序提供了一种与各种关系型数据库进行交互的标准方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package org.example.servlet;


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class JDBCexample {
public static void main(String[] args) throws Exception {
String jdbcURL = "jdbc:mysql://localhost:3306/willmo_cms";
String username = "root";
String password = "root";

try (Connection connection = DriverManager.getConnection(jdbcURL, username, password)) {
if (connection != null) {
System.out.println("Connected to the database!");
}
} catch (SQLException e) {
e.printStackTrace();
}
try (Connection connection = DriverManager.getConnection(jdbcURL, username, password);
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO test_table1 (id, Name,age) VALUES (12, 'JDBC插入测试',123)")) {


int rowsAffected = preparedStatement.executeUpdate();
if (rowsAffected > 0) {
System.out.println("A new row has been inserted.");
}
}
}
}

这对我们来说有几个问题

  • 繁冗赘余
  • SQL和java代码紧紧耦合在一起,难以分开处理

Mybatis正是为了解决这个问题。

Mybatis

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

使用

步骤 核心任务 关键产出 简要说明
1 项目初始化 pom.xml(Maven) 在项目中引入 MyBatis 及其数据库驱动等依赖。
2 核心配置 mybatis-config.xml 配置数据源(数据库连接)、事务管理器、以及映射文件路径等。
3 创建实体类 例如 User.java 创建与数据库表结构对应的 Java 类(POJO)。
4 创建Mapper接口 例如 UserMapper.java 定义数据操作的方法(如 selectUserById)。
5 编写SQL映射 例如 UserMapper.xml 在 XML 文件或注解中具体实现 SQL 语句,这一步是为了打开操作数据库的窗口。
6 使用SqlSession 测试代码 获取 SqlSession,得到 Mapper 接口实例,调用方法完成数据库操作。

引入依赖和配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.19</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>

Mybatis配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/willmo_cms"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<!-- 配置数据库的地址,操作账号和密码 -->
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="Mapper/UserMapper.xml"/>
</mappers>
</configuration>

创建用户类

1
2
3
4
5
6
7
8
9
10
package org.example.servlet;

import lombok.Data;

@Data
public class User {
public String name;
public int age;
public int id;
} //对应了数据库中存在的三个字段,姓名年龄ID

创建Mapper接口

1
2
3
4
5
6
7
8
9
10
11
12
package Mybatis_demo;

import org.example.servlet.User;

import java.util.*;

public interface UserMapper { //这仅仅是一个接口,方法主体的sql语句将在另一个SQL映射文件填充
List<User> list();
User GetById(int id);
void insert(User user);

}

创建SQL映射文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="Mybatis_demo.UserMapper">
<insert id="insert">
insert into test_table1(id,Name,age) values(#{id},#{name},#{age})
<!-- #{}是引用java方法中传入的参数名 -->
<!-- 在这里写sql语句尽量不要在末尾加分号-->
</insert>

<select id="list" resultType="org.example.servlet.User">
select id,Name,age from test_table1
</select>
<select id="GetById" resultType="org.example.servlet.User">
select id,Name,age from test_table1 where id = #{id}
</select>
</mapper>

使用sqlsession

这是在主方法直接使用Mybatis的部分,我们需要将sqlseesion实例化才能使用Mybatis对应的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package Mybatis_demo;

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 org.example.servlet.User;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class mybatisDemo {
public static void main(String[] args) {
// 读取配置文件
InputStream inputStream;

{
try {
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
throw new RuntimeException(e);
}
}

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //新建工厂类
// 获得会话
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> list = userMapper.list();
// 获取相关的执行器
System.out.println("总和用户数量为"+list.size());
User user1 = new User() ;
user1.setAge(90);
user1.setId(10);
user1.setName("插入测试");
userMapper.insert(user1);
sqlSession.commit();
for (int i = 1;i<= list.size()+1;i++) {
User user = userMapper.GetById(i);
System.out.println(user);
}
sqlSession.close();
}
}

Mybatis的核心配置

可以上滑查看Mybatis配置,这就是我们连接数据库的配置,它解释了连接哪个数据库,操作的账号密码,操作模式等等。为了后续的开发更轻松,我们有必要学习一些配置的技巧

配置是有顺序的,请保证顺序正确,否则会报错

类型别名

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

例如方法实现

1
<select id="list" resultType="org.example.servlet.User">

其中的 org.example.servlet.User是一个全限定类名,写起来可能有些繁琐,如果我们限定类名

<typeAliases> <typeAlias alias="User" type="org.example.servlet.User"/> </typeAliases>

就能够直接使用 User来确认返回值。

尊重xml语法,定义别名需要包含在typeAliases标签里。

SQL标签

resultType 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。
resultMap 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。

这两个指定返回值类型只能使用一个,他们都能够将数据库返回类型转换成java数据类型,但是resultMap更加灵活,可以指定字段的映射规则。

java方法传入参数名和数据库字段相同时,就能够自动映射完成sql语句的执行。但是有时候,java方法传入参数的字段,和数据库里的字段对不上,那就会造成错误,而resultmap能够给我们创建一个映射来解决这个问题。

1
2
3
4
<select id="Getuser" resultType="org.example.servlet.User">
select id,Name,age from test_table1 ;
</select>
</mapper>

但是你的用户类定义是

1
2
3
4
5
6
7
8
9
10
package org.example.servlet;

import lombok.Data;

@Data
public class User {
public String User_name;
public int age;
public int id;
}

这里的User_name和数据库字段Name对不上,我们就可以创建映射来使二者关联起来

1
2
3
<resultMap id = "userMap" type = "org.example.Servlet.User">
<result column = "Name" property = "User_name"></result>
</resultMap>

由此,我们的数据库字段和java中的User类就可以遵循各自的命名规范。

动态 SQL

动态 SQL 可以在 XML 层根据方法传入的参数动态拼装 SQL,避免手写大量不同情况的 SQL 字符串。常用标签:<if><where><trim><foreach>

在模糊查找的时候可用。

1. <if> — 条件片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="searchUsers" parameterType="map" resultType="org.example.domain.User">
SELECT id, Name, age
FROM test_table1
<where>
<if test="name != null and name != ''">
AND Name = #{name}
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
</where>
</select>
  • <if> 根据参数决定是否插入 SQL 片段。
  • 结合 <where> 自动处理开头的 AND/OR(见下)。

2. <where> — 自动处理前导逻辑运算符

<where> 会在包含的 SQL 片段前自动加 WHERE,并去除多余的前导 AND/OR
(上例演示 <where> 的常见用法)

3. <trim> — 更灵活的前后缀裁剪

1
2
3
4
5
6
7
8
<update id="updateUser" parameterType="org.example.domain.User">
UPDATE test_table1
<set>
<if test="name != null"> Name = #{name}, </if>
<if test="age != null"> age = #{age}, </if>
</set>
WHERE id = #{id}
</update>

或使用 <trim>

1
2
3
4
<trim prefix="SET" suffixOverrides="," >
<if test="name != null"> Name = #{name}, </if>
<if test="age != null"> age = #{age}, </if>
</trim>
  • <set><trim prefix="SET" suffixOverrides=",""> 的语法糖,适用于 UPDATE 的字段拼接。

4. <foreach> — 列表(IN)与批量插入

  • 用于 IN 子句:
1
2
3
4
5
6
7
<select id="getByIds" parameterType="list" resultType="org.example.domain.User">
SELECT id, Name, age FROM test_table1
WHERE id IN
<foreach item="id" collection="list" open="(" separator="," close=")">
#{id}
</foreach>
</select>
  • 用于批量插入(配合事务):
1
2
3
4
5
6
7
<insert id="batchInsert" parameterType="list">
INSERT INTO test_table1 (Name, age)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age})
</foreach>
</insert>
  • 使用批量插入时,注意数据库与驱动的批量性能,必要时启用 JDBC 批处理或分批提交以避免内存过大。

性能 / 安全

1. 使用 #{} 避免 SQL 注入(不要用 ${}

  • #{}:使用预编译参数,自动进行类型处理与转义,它会将用户输入的数据以字符串的形式而不是sql命令输入控制台,能有效防止 SQL 注入。

    1
    WHERE Name = #{name}
  • ${}:直接字符串替换(危险),仅在必须动态拼接 SQL 片段(如表名、列名)且确保来源受控时使用。

    1
    2
    <!-- 不要这样处理用户输入 -->
    WHERE Name = '${name}'
  • 总结:对用户输入的值一律用 #{};只有在拼接 SQL 语法(表名、排序字段等)且已校验白名单时再考虑 ${}

2. 批量插入与性能

  • 对大量数据的插入,使用 <foreach> 批量插入比逐条插入更高效;也可以结合 JDBC 批处理(ExecutorType.BATCH)或分批次提交(例如每 500 条 commit 一次)。
  • 注意:一次性插入过多记录可能导致单条 SQL 较大或内存问题,建议分批。

3. 索引与 SQL 优化(建议)

  • 为常用的 WHERE/ORDER BY 字段建立索引;对慢查询使用 EXPLAIN 分析。
  • 避免 SELECT *(按需列出),减少传输与解析开销。