关于Mybatis和JDBC的使用及区别
目录
- 1、JDBC
- 1.1、流程
- 1.2、优缺点
- 2、MyBATis
- 2.1、执行流程
- 2.2、使用
- 2.3、实现方式
- 1、XML配置文件
- 2、注解
- 3、动态 SQL
- 4、MyBatis-Plus 扩展实现
- 3、缓存
- 3.1. 一级缓存
- 3.2. 二级缓存
- 3.3. 缓存区别
- 4、Sql注入
- 4.1.#{}参数占位符
- 4.2.${}字符串拼接
- 4.3.SQL注入
- 4.4.用法总结
- 5、懒加载
- 6、常用问题
- 6.1、映射不一致
- 1、使用 XML(resultMap)
- 2、使用(Results)
- 3、jsonProperty
- 6.2、模糊查询
- 6.3、动态标签
- 总结
基于Java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
1、JDBC
1.1、流程
下面是一个简单的示例,演示如何使用 JDBC 直接与数据库进行交互:
1. Maven 依赖(如果使用 Maven)
如果您的项目使用 Maven,确保在 pom.xml 中包含 JDBC 驱动的依赖,例如使用 mysql:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version> <!-- 根据您需要的版本选择 -->
</dependency>
2. 加载数据库驱动和管理 SQL
import java.sql.*;
public class JdbcExample {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 1. 加载数据库驱动
Class.forName("com.mysql.cj.jdbc.Driver"); // 替换为相应驱动
// 2. 建立数据库连接
String url = "jdbc:mysql://localhost:3306/yourdatabase"; // 数据库 URL
String user = "yourusername"; // 数据库用户名
String password = "yourpassword"; // 数据库密码
connection = DriverManager.getConnection(url, user, password);
// 3. 创建 PreparedStatement
String sql = "SELECT * FROM users WHERE id = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setLong(1, 1); // 设置参数
// 4. 执行查询
resultSet = preparedStatement.executeQuery();
// 5. 处理结果
while (resultSet.next()) {
System.out.println("User ID: " + resultSet.getInt("id"));
System.out.println("User Name: " + resultSet.getString("name"));
System.out.println("User Email: " + resultSet.getString("email"));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭资源
try {
if (resultSet != null) resultSet.close();
if (preparedStatement != null) preparedStatement.close();
if (connection != null) connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
从以上代码展示,可以看到jdbc的流程:
- 加载数据库驱动:加载与所使用数据库对应的 JDBC 驱动。
- 建立数据库连接:使用
DriverManager.getConnection()方法建立与数据库的连接。 - 创建
Statement或PreparedStatement:用于执行 SQL 查询或更新。 - 执行 SQL 语句:通过
executeQuery()或executeUpdate()方法执行 SQL 语句。 - 处理结果:处理查询结果 (如果有)。
- 关闭资源:关闭
ResultSet、Statement、Connection等资源,防止资源泄漏。
1.2、优缺点
1.优点:
- 直接灵活:可以完全控制 SQL 语句和数据库交互的细节。
- 简单:适合小型应用或简单查询。
2.缺点:
- 冗长:代码冗长,尤其是在处理事务和异常时,容易导致代码重复。
- 不易维护:SQL 语句与业务逻辑混杂,导致难以维护和管理。
- 手动管理:需要手动处理资源的关闭,容易出现资源泄漏。
- 缺乏抽象:不支持对象关系映射,处理复杂数据结构时不够方便。
因此基于以上的现象,mybatis或者Hibernate就可以满足更复杂的要求,且统一管理,便于维护。
2、Mybatis
2.1、执行流程

2.2、使用
下面是一个使用 MyBatis 连接 MySQL 数据库的完整示例。这个示例展示了如何使用 MyBatis 进行基本的 CRUD 操作,同时使用 MySQL 作为数据库。
项目结构:
mybatis-mysql-example
│├── src│ ├── main│ │ ├── java│ │ │ └── com│ │ │ └── example│ │ │ ├── Main.java│ │ │ ├── User.java│ │ │ ├── UserMapper.java│ │ │ └── MyBatisUtils.java│ │ └── resources│ │ ├── mybatis-config.xml│ │ └── mapper│ │ └── UserMapper.xml└── pom.xml
1. Maven 依赖
确保在 pom.xml 中添加 MyBatis 和 MySQL 的 JDBC 驱动的依赖:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>mybatis-mysql-example</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>11</java.version>
<mybatis.version>3.5.7</mybatis.version>
<mysql.version>8.0.25</mysql.version> <!-- 使用适合你的版本 -->
</properties>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. 创建用户实体类(User.java)
package com.example;
public class User {
private Long id;
private String username;
private String email;
// Getters 和 Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
3. 创建 Mapper 接口(UserMapper.java)
package com.example;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface UserMapper {
void insertUser(User user);
User getUser(Long id);
List<User> getAllUsers();
List<User> getUsersByPage(@Param("offset") int offset, @Param("limit") int limit); // 分页查询
void updateUser(User user);
void deleteUser(Long id);
}
4. 创建 MyBatis 配置文件(mybatis-config.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org/DTD Mapper 3.0//EN"
"http://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/yourdatabase?useSSL=false&serverTimezone=UTC"/>
<property name="username" value="yourusername"/>
<property name="password" value="yourpassword"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
5. 创建 Mapper XML 文件(UserMapper.xml)
在 src/main/resources/mapper 目录下创建 UserMapper.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.UserMapper">
<insert id="insertUser" parameterType="com.example.User">
INSERT INTO users (username, email) VALUES (#{username}, #{email})
</insert>
<select id="getUser" resultType="com.example.User" parameterType="long">
SELECT * FROM users WHERE id = #{id}
</select>
<select id="getAllUsers" resultType="com.example.User">
SELECT * FROM users
</select>
<select id="getUsersByPage" resultType="com.example.User">
SELECT * FROM users LIMIT #{limit} OFFSET #{offset} <!-- 分页查询 -->
</select>
<update id="updateUser" parameterType="com.example.User">
UPDATE users SET username = #{username}, email = #{email} WHERE id = #{id}
</update>
<delete id="deleteUser" parameterType="long">
DELETE FROM users WHERE id = #{id}
</delete>
</mapper>
6. 创建 MyBatis 实用工具类(MyBatisUtils.java)
package com.example;
import org.apache.ibatis.session.SqlSession;
import orgmgIpm.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
// 读取 mybatis-config.xml 配置文件
InputStream inputStream = MyBatisUtils.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
7. 创建主类(Main.java)
这是程序的入口,用于执行 CRUD 操作:
您可以创建一个服务类来处理业务逻辑,包括分页查询:
package com.example;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class UserService {
public List<User> getUsersByPage(int pageNumber, int pageSize) {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int offset = (pageNumber - 1) * pageSize; // 计算偏移量
return userMapper.getUsersByPage(offset, pageSize); // 调用分页方法
} finally {
sqlSession.close(); // 关闭 SQL 会话
}
}
}
调用分页查询方法并打印结果:
package com.example;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class Main {
public static void main(String[] args) {
// Initialize MyBatis and data
UserService userService = new UserService();
// 进行分页查询
int pageNumber = 1; // 页码
int pageSize = 5; // 每页条数
python List<User> users = userService.getUsersByPage(pageNumber, pageSize);
// 打印结果
System.out.println("Users on Page " + pageNumber + ":");
for (User user : users) {
System.out.println("- " + user.getUsername());
}
}
}
其他接口可以使用以下方法查询:
package com.example;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class Main {
public static void main(String[] args) {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 插入用户
User user = new User();
user.setUsername("john_doe");
user.setEmail("john@example.com");
userMapper.insertUser(user);
sqlSession.commit();
// 查询用户
User retrievedUser = userMapper.getUser(1L);
System.out.println("Retrieved User: " + retrievedUser.getUsername());
// 查询所有用户
List<User> users = userMapper.getAllUsers();
System.out.println("All Users:");
for (User u : users) {
System.out.println(u.getUsername());
}
// 更新用户
user.setEmail("john_doe_updated@example.com");
userMapper.updateUser(user);
sqlSession.commit();
// 删除用户
userMapper.deleteUser(1L);
sqlSession.commit();
} finally {
sqlSession.close(); // 确保资源关闭
}
}
}
8. 创建 MySQL 数据库和表
在 MySQL 数据库中,您需要创建数据库和用户表。假设您创建了一个名为 yourdatabase 的数据库,执行以下 SQL 语句:
CREATE DATABASE yourdatabase;
USE yourdatabase;
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100),
email VARCHAR(100)
);
9、运行程序。
2.3、实现方式
1、xml配置文件
通过 .xml 文件定义 SQL 语句和映射关系,与 Mapper 接口绑定。
<!-- UserMapper.xml -->
<select id="getUserById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
2、注解
直接在 Mapper 接口的方法上使用注解(如 @Select、@Update)编写 SQL。
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(Long id);
3、动态 SQL
早期 MyBatis 注解对动态 SQL 支持较弱,但通过 @SelectProvider、@UpdateProvider 或者xml方式等注解,现已能实现复杂逻辑。
XML 动态标签
通过 XML 中的 <if>、<choose>、<foreach> 等标签实现条件逻辑。
代码示例:
<select id="searchUsers">
SELECT * FROM user
<where>
<if test="name != null">AND name = #{name}</if>
<if test="role != null">AND role = #{role}</if>
</where>
</select>
注解结合 Provider 类
使用 @SelectProvider、@UpdateProvider 注解,调用工具类生成动态 SQL:
代码示例:
@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUserByName")
User getUserByName(@Param("name") String name);
class UserSqlBuilder {
public String buildGetUserByName(Map<String, Object> params) {
return new SQL() {{
SELECT("*");
FROM("user");
WHERE("name = #{name}");
if (params.get("role") != null) {
AND().WHERE("role = #{role}");
}
}}.toString();
}
}
4、MyBatis-Plus 扩展实现
基于 MyBatis 的增强工具,提供通用 Mapper(BaseMapper)和 ActiveRecord 模式,减少手写 SQL。
// 继承 BaseMapper 直接调用内置方法
public interface UserMapper extends BaseMapper<User> {}
// 使用 ActiveRecord 模式
User user = new User();
user.setName("John");
user.insert();
总结

3、缓存
缓存的查找顺序:二级缓存 => 一级缓存 => 数据库。

3.1. 一级缓存
一级缓存是SqlSession级别的缓存,默认开启,默认是 SESSION 级

1.原理
每个SqlSession中持有了Executor,每个Executor中有一个LocalCache。
当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。
Local Cache 其实是一个 hashmap 的结构:
private Map<Object, Object> cache = new HashMap<Object, Object>();

2.核心流程
- 第一次查询:从数据库读取数据,存入一级缓存。
- 第二次相同查询:直接读取缓存,不访问数据库。
- 执行更新操作:清空一级缓存,后续查询重新访问数据库。
代码示例:
// 示例代码
public static void main(String[] args) {
SqlSessionFactory factory = ...; // 获取 SqlSessionFactory
try (SqlSession session = factory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
System.out.println("--- 第一次查询 ---");
User user1 = mapper.getUserById(1L); // 查询数据库
System.out.println("查询结果: " + user1);
System.out.println("--- 第二次相同查询 ---");
User user2 = mapper.getUserById(1L); // 从一级缓存读取
System.out.println("查询结果: " + user2);
System.out.println("user1 == user2? " + (user1 == user2)); // 输出 true
System.out.println("--- 执行更新操作 ---");
user1.setName("NewName");
mapper.updateUser(user1); // 清空一级缓存
session.commit(); // 提交事务
System.out.println("--- 第三次查询 ---");
User user3 = mapper.getUserById(1L); // 重新查询数据库
System.out.println("查询结果: " + user3);
}
}
预期输出:
--- 第一次查询 ---
DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id = ?DEBUG [main] - ==> Parameters: 1(Long)DEBUG [main] - <== Total: 1查询结果: User(id=1, name=OldName)--- 第二次相同查询 ---
查询结果: User(id=1, name=OldName)user1 == user2? true // 直接从缓存获取,无 SQL 日志--- 执行更新操作 ---
DEBUG [main] - ==> Preparing: UPDATE user SET name = ? WHERE id = ?DEBUG [main] - ==> Parameters: NewName(String), 1(Long)DEBUG [main] - <== Updates: 1--- 第三次查询 ---
DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id = ?DEBUG [main] - ==> Parameters: 1(Long)DEBUG [main] - <== Total: 1查询结果: User(id=1, name=NewName)
关键点
- 两次查询结果相同:第二次未触发 SQL,直接返回缓存对象(
user1 == user2)。 - 更新操作后缓存失效:第三次查询重新访问数据库。
3.缓存失效
不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何一次增删改操作
- 同一个SqlSession两次查询期间手动清空了缓存
3.2. 二级缓存
二级缓存是NameSpace级别(Mapper)的缓存,多个SqlSession可以共享,使用时需要进行配置开启,同一个namespace影响同一个cache。

1.执行顺序
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- SqlSession关闭之后,一级缓存中的数据会写入二级缓存。
代码示例:
public static void main(String[] args) {
SqlSessionFactory factory = ...; // 已开启二级缓存
// 第一个会话(查询并提交)
try (SqlSession session1 = factory.openSession()) {
UserMapper mapper1 = session1.getMapper(UserMapper.class);
System.out.println("--- SqlSession1 查询 ---");
User user1 = mapper1.getUserById(1L); // 查询数据库
System.out.println("查询结果: " + user1);
session1.commit(); // 提交后同步到二级缓存
}
// 第二个会话(相同查询)
try (SqlSession session2 = factory.openSession()) {
UserMapper mapper2 = session2.getMapper(UserMapper.class);
System.out.println("--- SqlSession2 查询 ---");
User user2 = mapper2.getUserById(1L); // 从二级缓存读取
System.out.println("查询结果: " + user2);
}
// 第三个会话(更新数据)
try (SqlSession session3 = factory.openSession()) {
UserMapper mapper3 = session3.getMapper(UserMapper.class);
System.out.println("--- SqlSession3 更新 ---");
User user = new User(1L, "UpdatedName");
mapper3.updateUser(user); // 清空二级缓存
session3.commit();
}
// 第四个会话(重新查询)
try (SqlSession session4 = factory.openSession()) {
UserMapper mapper4 = session4.getMapper(UserMapper.class);
System.out.println("--- SqlSession4 查询 ---");
User user4 = mapper4.getUserById(1L); // 重新查询数据库
System.out.println("查询结果: " + user4);
}
}
预期输出:
--- SqlSession1 查询 ---
DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id = ?DEBUG [main]编程客栈 - ==> Parameters: 1(Long)DEBUG [main] - <== Total: 1查询结果: User(id=1, name=OldName)--- SqlSession2 查询 ---
查询结果: User(id=1, name=OldName) // 无 SQL 日志,直接读二级缓存--- SqlSession3 更新 ---
DEBUG [main] - ==> Preparing: UPDATE user SET name = ? WHERE id = ?DEBUG [main] - ==> Parameters: UpdatedName(String), 1(Long)DEBUG [main] - <== Updates: 1--- SqlSession4 查询 ---
DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id = ?DEBUG [main] - ==> Parameters: 1(Long)DEBUG [main] - <== Total: 1查询结果: User(id=1, name=UpdatedName)
关键点
- 跨会话共享缓存:
SqlSession2未访问数据库。 - 更新操作清空缓存:
SqlSession3更新后,SqlSession4重新查询数据库。
2、配置二级缓存(关键步骤)
1.实体类实现 Serializable:
public class User implements Serializable {
// ...
}
2.Mapper XML 中添加 <cache/>:
<mapper namespace="com.example.UserMapper">
<cache eviction="LRU" flushInterval="60000" size="1024"/>
</mapper>
3.3. 缓存区别

4、Sql注入
4.1.#{}参数占位符
#{} 是 MyBatis 中的 参数占位符,用于将参数传递到 SQL 语句中。 会将 #{} 中的参数值自动进行转义,将 sql 中的#{}替换为 ? 号。
作用是将参数值进行处理并填充到 SQL 语句中,在传递参数时会进行预处理,避免 SQL 注入风险。
4.2.${}字符串拼接
${} 是 MyBatis 中的 字符串拼接占位符,用于直接将参数值拼接到 SQL 语句中。
它会将传入的参数值直接替换到 SQL 中的 ${} 所在的位置,而不会进行任何的处理或转义。
由下图所说:

显然#{}直接去数据库查询,是查不到这个username,而${}由于没有进行转义处理,就会执行1=1;
4.3.SQL注入
MyBatis的#{}之所以能够预防SQL注入是因为底层使用了PreparedStatement类的setString()方法来设置参数,此方法会获取传递进来的参数的每个字符,然后进行循环对比,如果发现有敏感字符(如:单引号、双引号等),则会在前面加上一个'/'代表转义此符号,让其变为一个普通的字符串,不参与SQL语句的生成,达到防止SQL注入的效果。
其次${}本身设计的初衷就是为了参与SQL语句的语法生成,自然而然会导致SQL注入的问题(不会考虑字符过滤问题)。
setString方法为什么可以解决。
public void setString(int parameterIndex, String x) throws SQLException {
synchronized(this.checkClosed().getConnectionMutex()) {
if (x == null) {
this.setNull(parameterIndex, 1);
} else {
this.checkClosed();
int stringLength = x.length();
StringBuilder buf;
if (this.connection.isNoBackslashEscapesSet()) {
boolean needsHexEscape = this.isEscapeNeededForString(x, stringLength);
Object parameterAsBytes;
byte[] parameterAsBytes;
if (!needsHexEscape) {
parameterAsBytes = null;
buf = new StringBuilder(x.length() + 2);
buf.append('\'');
buf.append(x);
buf.append('\'');
if (!this.isLoadDataQuery) {
parameterAsBytes = StringUtils.getBytes(buf.toString(), this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
} else {
parameterAsBytes = StringUtils.getBytes(buf.toString());
}
this.setInternal(parameterIndex, parameterAsBytes);
} else {
parameterAsBytes = null;
if (!this.isLoadDataQuery) {
parameterAsBytes = StringUtils.getBytes(x, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
} else {
parameterAsBytes = StringUtils.getBytes(x);
}
this.setBytes(parameterIndex, parameterAsBytes);
}
return;
}
String parameterAsString = x;
boolean needsQuoted = true;
if (this.isLoadDataQuery || this.isEscapeNeededForString(x, stringLength)) {
needsQuoted = false;
buf = new StringBuilder((int)((double)x.length() * 1.1D));
buf.append('\'');
for(int i = 0; i < stringLength; ++i) { //遍历字符串,获取到每个字符
char c = x.charAt(i);
switch(c) {
case '\u0000':
buf.append('\\');
buf.append('0');
break;
case '\n':
buf.append('\\');
buf.append('n');
break;
case '\r':
buf.append('\\');
buf.append('r');
break;
case '\u001a':
buf.append('\\');
buf.append('Z');
break;
case '"':
if (this.usingAnsiMode) {
buf.append('\\');
}
buf.append('"');
break;
case '\'':
buf.append('\\');
buf.append('\'');
break;
case '\\':
buf.append('\\');
buf.append('\\');
break;
case '':
case '₩':
if (this.charsetEncoder != null) {
CharBuffer cbuf = CharBuffer.allocate(1);
ByteBuffer bbuf = ByteBuffer.allocate(1);
cbuf.put(c);
cbuf.position(0);
this.charsetEncoder.encode(cbuf, bbuf, true);
if (bbuf.get(0) == 92) {
buf.append('\\');
}
}
buf.append(c);
break;
default:
buf.append(c);
}
}
buf.append('\'');
parameterAsString = buf.toString();
}
buf = null;
byte[] parameterAsBytes;
if (!this.isLoadDataQuery) {
if (needsQuoted) {
parameterAsBytes = StringUtils.getBytesWrapped(parameterAsString, '\'', '\'', this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
} else {
parameterAsBytes = StringUtils.getBytes(parameterAsString, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
}
} else {
parameterAsBytes = StringUtils.getBytes(parameterAsString);
}
this.setInternal(parameterIndex, parameterAsBytes);
this.parameterTypes[parameterIndex - 1 + this.getParameterIndexOffset()] = 12;
}
}
}
4.4.用法总结
1)#{}在使用时,会根据传递进来的值来选择是否加上双引号,因此我们传递参数的时候一般都是直接传递,不用加双引号,${}则不会,我们需要手动加。
2)在传递一个参数时,我们说了#{}中可以写任意的值,${}则必须使用value;即:${value}
3)#{}针对SQL注入进行了字符过滤,${}则只是作为普通传值,并没有考虑到这些问题
4)#{}的应用场景是为给SQL语句的where字句传递条件值,${}的应用场景是为了传递一些需要参与SQL语句语法生成的值。
5、懒加载
MyBatis 的懒加载功能允许在需要时才加载关联的对象,从而提高系统的性能。懒加载是一种常用的策略,尤其适用于处理大型数据集时,避免在查询时加载不必要的数据。
5.1、原理
在 MyBatis 中,懒加载是通过延迟加载关联对象的策略实现的。当你访问一个未初始化的属性时,MyBatis 会自动从数据库加载相关的数据,而不是在首次加载父对象时就将所有的关联数据一起加载。
5.2、开启
全局配置: 你可以在 MyBatis 的全局配置文件 mybatis-config.xml 中设置懒加载功能。
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/> <!-- 控制是否在访问懒加载对象时立即加载 -->
</settings>
</configuration>
lazmgIpmyLoadingEnabled: 设置为true时启用懒加载。aggressiveLazyLoading: 如果设置为true,当你访问一个对象的任何属性时,会立即加载所有懒加载的属性。如果设置为false,那么只有在访问特定的懒加载属性时才会加载。

5.3、实现
假设我们有两个实体:User 和 Order,每个用户可以有多个订单。这是一个常见的一对多关系的例子。
1. 数据库表结构
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL
);
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
order_number VARCHAR(50) NOT NULL,
user_id INT,
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
2. POJO 类
User.java
import java.util.List;
public class User {
private Integer userId;
private String username;
private List<Order> orders; // 一对多关联
// Getters 和 Setters
// ...
}
Order.java
public class Order {
private Integer orderId;
private String orderNumber;
// Getters 和 Setters
// ...
}
3. Mapper 接口
我们创建一个 Mapper 接口来定义查询方法。
package com.example.mapper;
import com.example.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserMapper {
@Select("SELECT * FROM users WHERE user_id = #{userId}")
User findUserById(Integer userId);
}
4. XML 映射文件
为了懒加载订单数据,我们可以在 User 类中定义一个方法来获取订单。如果你不希望在获取用户时立即加载用户的所有订单数据,可以通过以下方式进行设计。
UserMapper.xml
<mapper namespace="com.example.mapper.UserMapper">
<resultMap id="userResultMap" type="User">
<id property="userId" column="user_id"/>
<result property="username" column="username"/>
<collection property="orders" ofType="Order"
select="com.example.mapper.OrderMapper.findOrdersByUserId"
fetchType="lazy"/> <!-- 使用懒加载 -->
</resultMap>
<select id="findUserById" resultMap="userResultMap">
SELECT * FROM users WHERE user_id = #{userId}
</select>
</mapper>
- 在
UserMapper.xml中,<collection>标签表示与Order的一对多关系的懒加载配置。通过select属性指定查询订单的 SQL。 fetchType="lazy"表示此集合数据将延迟加载。
OrderMapper.java
package com.example.mapper;
import com.example.model.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface OrderMapper {
@Select("SELECT * FROM orders WHERE user_id = #{userId}")
List<Order> findOrdersByUserId(Integer userId);
}
5. 使用懒加载的情况
在你的服务层中,你可以调用 findUserById 方法。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User getUserWithOrders(Integer userId) {
User user = userMapper.findUserById(userId);
// Orders 还没有被加载,直到你调用 user.getOrders()
return user;
}
}
6. 控制器层
你可以使用 Spring MVC 来创建一个控制器,处理 HTTP 请求。
UserController.java:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getUser(@PathVariable Integer id) {
return userService.getUserWithOrders(id);
}
}
7. 使用示例
当你访问 http://localhost:8080/users/1,会调用 getUser() 方法。
- 一开始,
findUserById方法会查询用户信息,但订单还不会被加载。 - 当你访问
user.getOrders()时,MyBatis 会自动调用OrderMapper.findOrdersByUserId方法来查询与该用户相关的订单。
8. 总结
以上代码演示了如何在 MyBatis 中实现懒加载功能。通过配置 XML 文件中的 <collection> 标签结合 fetchType="lazy" 设置,可以确保在访问相关集合属性时才加载数据。这能帮助提高性能,减少不必要的数据加载。
懒加载机制在处理大数据量、频繁不使用的关联对象时尤为重要,有效节约系统资源。
6、常用问题
6.1、映射不一致
当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
使用 @Results 注解或者 <resultMap> 来明确映射关系。
假设我们有一个数据库表 users,其结构如下:

在项目中,有一个对应的 POJO 类 User,其属性使用驼峰命名风格:
public class User {
private Integer id; // 对应 user_id
private String username; // 对应 user_name
private String email; // 对应 user_email
get set方法略
}
1、使用 XML(resultMap)
1. 创建 MyBatis 映射文件
将使用 XML 文件 UserMapper.xml 来定义 SQL 操作和字段映射。这个文件通常位于 resources 目录下。
UserMapper.xml:
<mapper namespace="com.example.mapper.UserMapper">
<!-- 定义结果映射 -->
<resultMap id="UserResultMap" type="User">
<result property="id" column="user_id"/>
<result property="username" column="user_name"/>
<result property="email" column="user_email"/>
</resultMap>
<!-- 查询用户的 SQL 操作 -->
<select id="getUserById" resultMap="UserResultMap">
SELECT user_id, user_name, user_email FROM users WHERE user_id = #{id}
</select>
</mapper>
2、使用(Results)
如果你想使用注解的方式,可以直接在 Mapper 接口中使用 @Select 和 @Results 注解来实现。下面是一个示例:
UserMapper.java(注解方式):
package com.example.mapper;
import com.example.model.User;
import org.apache.ibatis.annotations.*;
@Mapper
public interface UserMapper {
@Select("SELECT user_id, user_name, user_email FROM users WHERE user_id = #{id}")
@Results({
@Result(property = "id", column = "user_id"),
@Result(property = "username", column = "user_name"),
@Result(property = "email", column = "user_email")
})
User getUserById(Integer id);
}
3、JsonProperty
@JsonProperty("user_id") // 将 JSON 中的 user_id 映射到 id
private Integer id;
@JsonProperty("user_name") // 将 JSON 中的 user_name 映射到 username
private String username;
@JsonProperty("user_email") // 将 JSON 中的 user_email 映射到 email
private String email;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
// 创建一个 User 对象
User user = new User();
user.setId(1);
user.setUsername("alice");
user.setEmail("alice@example.com");
// 将 User 对象序列化为 JSON 字符串
String jsonString = objectMapper.writeValueAsString(user);
System.out.println("Serialized JSON: " + jsonString);
}
}
输出:
{"user_id":1,"user_name":"alice","user_email":"alice@example.com"}
package com.example.mapper;
import com.example.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface UserMapper {
List<User> findUsersByUsernameLike(@Param("username") String username);
}
@JsonProperty 注解帮助在序列化和反序列化过程中,指定 JSON 字段与 POJO 属性之间的映射关系。
6.2、模糊查询
1.CONCAT
创建一个 Mapper 接口,并添加一个模糊查询的方法。
package com.example.mapper;
import com.example.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface UserMapper {
List<User> findUsersByUsernameLike(@Param("username") String username);
}
在 Mapper XML 文件中定义模糊查询的 SQL 语句。我们将使用 LIKE 关键字进行模糊匹配。
UserMapper.xml:
<mapper namespace="com.example.mapper.UserMapper">
<select id="findUsersByUsernameLike" resultType="User">
SELECT user_id, username, email
FROM users
WHERE username LIKE CONCAT('%', #{username}, '%')
</select>
</mapper>
2.传递参数%value%
在传入参数的时候比如username:%value% 传入到mapper文件里面;
6.3、动态标签
1. <if>
用于判断条件,如果条件满足则会生成 SQL 语句中的代码。
示例:
<select id="findUserByCriteria" resultType="User">
SELECT * FROM users
WHERE 1=1
<if test="username != null">
AND username = #{username}
</if>
<if test="email != null">
AND email = #{email}
</if>
</select>
在这个例子中,只有当 username 或 email 不为 null 时,相应的条件才会被添加到 SQL 中。
2. <choose>、<when> 和 <otherwise>
用于多条件选择的情况,类似于 Java 中的 switch-case 语句。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public List<User> findUsers(String status) {
return userMapper.findUsersByStatus(status);
}
}
<select id="findUserByStatus" resultType="User">
SELECT * FROM users
WHERE
<choose>
<when test="status == 'active'">
status = 'active'
</when>
<when test="status == 'inactive'">
status = 'inactive'
</when>
<otherwise>
status IS NOT NULL
</otherwise>
</choose>
</select>
在这个例子中,SQL 查询将会根据 status 的值选择生成查询条件。
3.<foreach>
用于循环遍历集合,通常用于生成 IN 列表或多条 SQL 语句。
示例:
<select id="findUsersByIds" resultType="User">
SELECT * FROM users
WHERE user_id IN
<foreach item="id" collection="userIds" open="(" separator="," close=")">
#{id}
</foreach>
</select>
4. <trim>
用于去掉 SQL 语句前后可能出现的多余的字符,主要是在构建可选条件时使用,查询参数可以是可选的。
<select id="findUsersByCondition" resultType="User">
SELECT android* FROM users
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="username != null">
AND username = #{username}
</if>
<if test="email != null">
AND email = #{email}
</if>
</trim>
</select>
<trim> 可以去掉多余的前缀,比如如果没有设定 username 和 email,可以避免生成 WHERE 后面紧跟着的 AND。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。
加载中,请稍侯......
精彩评论