關(guān)于Mybatis和JDBC的使用及區(qū)別
基于java的持久層框架,它內(nèi)部封裝了jdbc,使開發(fā)者只需要關(guān)注sql語句本身,而不需要花費(fèi)精力去處理加載驅(qū)動(dòng)、創(chuàng)建連接、創(chuàng)建statement等繁雜的過程。
1、JDBC
1.1、流程
下面是一個(gè)簡(jiǎn)單的示例,演示如何使用 JDBC 直接與數(shù)據(jù)庫進(jìn)行交互:
1. Maven 依賴(如果使用 Maven)
如果您的項(xiàng)目使用 Maven,確保在 pom.xml 中包含 JDBC 驅(qū)動(dòng)的依賴,例如使用 MySQL:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version> <!-- 根據(jù)您需要的版本選擇 -->
</dependency>2. 加載數(shù)據(jù)庫驅(qū)動(dòng)和管理 SQL
import java.sql.*;
public class JdbcExample {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 1. 加載數(shù)據(jù)庫驅(qū)動(dòng)
Class.forName("com.mysql.cj.jdbc.Driver"); // 替換為相應(yīng)驅(qū)動(dòng)
// 2. 建立數(shù)據(jù)庫連接
String url = "jdbc:mysql://localhost:3306/yourdatabase"; // 數(shù)據(jù)庫 URL
String user = "yourusername"; // 數(shù)據(jù)庫用戶名
String password = "yourpassword"; // 數(shù)據(jù)庫密碼
connection = DriverManager.getConnection(url, user, password);
// 3. 創(chuàng)建 PreparedStatement
String sql = "SELECT * FROM users WHERE id = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setLong(1, 1); // 設(shè)置參數(shù)
// 4. 執(zhí)行查詢
resultSet = preparedStatement.executeQuery();
// 5. 處理結(jié)果
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. 關(guān)閉資源
try {
if (resultSet != null) resultSet.close();
if (preparedStatement != null) preparedStatement.close();
if (connection != null) connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}從以上代碼展示,可以看到j(luò)dbc的流程:
- 加載數(shù)據(jù)庫驅(qū)動(dòng):加載與所使用數(shù)據(jù)庫對(duì)應(yīng)的 JDBC 驅(qū)動(dòng)。
- 建立數(shù)據(jù)庫連接:使用
DriverManager.getConnection()方法建立與數(shù)據(jù)庫的連接。 - 創(chuàng)建
Statement或PreparedStatement:用于執(zhí)行 SQL 查詢或更新。 - 執(zhí)行 SQL 語句:通過
executeQuery()或executeUpdate()方法執(zhí)行 SQL 語句。 - 處理結(jié)果:處理查詢結(jié)果 (如果有)。
- 關(guān)閉資源:關(guān)閉
ResultSet、Statement、Connection等資源,防止資源泄漏。
1.2、優(yōu)缺點(diǎn)
1.優(yōu)點(diǎn):
- 直接靈活:可以完全控制 SQL 語句和數(shù)據(jù)庫交互的細(xì)節(jié)。
- 簡(jiǎn)單:適合小型應(yīng)用或簡(jiǎn)單查詢。
2.缺點(diǎn):
- 冗長(zhǎng):代碼冗長(zhǎng),尤其是在處理事務(wù)和異常時(shí),容易導(dǎo)致代碼重復(fù)。
- 不易維護(hù):SQL 語句與業(yè)務(wù)邏輯混雜,導(dǎo)致難以維護(hù)和管理。
- 手動(dòng)管理:需要手動(dòng)處理資源的關(guān)閉,容易出現(xiàn)資源泄漏。
- 缺乏抽象:不支持對(duì)象關(guān)系映射,處理復(fù)雜數(shù)據(jù)結(jié)構(gòu)時(shí)不夠方便。
因此基于以上的現(xiàn)象,mybatis或者Hibernate就可以滿足更復(fù)雜的要求,且統(tǒng)一管理,便于維護(hù)。
2、Mybatis
2.1、執(zhí)行流程

2.2、使用
下面是一個(gè)使用 MyBatis 連接 MySQL 數(shù)據(jù)庫的完整示例。這個(gè)示例展示了如何使用 MyBatis 進(jìn)行基本的 CRUD 操作,同時(shí)使用 MySQL 作為數(shù)據(jù)庫。
項(xiàng)目結(jié)構(gòu):
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 驅(qū)動(dòng)的依賴:
<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. 創(chuàng)建用戶實(shí)體類(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. 創(chuàng)建 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. 創(chuàng)建 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. 創(chuàng)建 Mapper XML 文件(UserMapper.xml)
在 src/main/resources/mapper 目錄下創(chuàng)建 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. 創(chuàng)建 MyBatis 實(shí)用工具類(MyBatisUtils.java)
package com.example;
import org.apache.ibatis.session.SqlSession;
import org.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. 創(chuàng)建主類(Main.java)
這是程序的入口,用于執(zhí)行 CRUD 操作:
您可以創(chuàng)建一個(gè)服務(wù)類來處理業(yè)務(wù)邏輯,包括分頁查詢:
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; // 計(jì)算偏移量
return userMapper.getUsersByPage(offset, pageSize); // 調(diào)用分頁方法
} finally {
sqlSession.close(); // 關(guān)閉 SQL 會(huì)話
}
}
}調(diào)用分頁查詢方法并打印結(jié)果:
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();
// 進(jìn)行分頁查詢
int pageNumber = 1; // 頁碼
int pageSize = 5; // 每頁條數(shù)
List<User> users = userService.getUsersByPage(pageNumber, pageSize);
// 打印結(jié)果
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(); // 確保資源關(guān)閉
}
}
}8. 創(chuàng)建 MySQL 數(shù)據(jù)庫和表
在 MySQL 數(shù)據(jù)庫中,您需要?jiǎng)?chuàng)建數(shù)據(jù)庫和用戶表。假設(shè)您創(chuàng)建了一個(gè)名為 yourdatabase 的數(shù)據(jù)庫,執(zhí)行以下 SQL 語句:
CREATE DATABASE yourdatabase;
USE yourdatabase;
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100),
email VARCHAR(100)
);9、運(yùn)行程序。
2.3、實(shí)現(xiàn)方式
1、xml配置文件
通過 .xml 文件定義 SQL 語句和映射關(guān)系,與 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、動(dòng)態(tài) SQL
早期 MyBatis 注解對(duì)動(dòng)態(tài) SQL 支持較弱,但通過 @SelectProvider、@UpdateProvider 或者xml方式等注解,現(xiàn)已能實(shí)現(xiàn)復(fù)雜邏輯。
XML 動(dòng)態(tài)標(biāo)簽
通過 XML 中的 <if>、<choose>、<foreach> 等標(biāo)簽實(shí)現(xiàn)條件邏輯。
代碼示例:
<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>注解結(jié)合 Provider 類
使用 @SelectProvider、@UpdateProvider 注解,調(diào)用工具類生成動(dòng)態(tài) 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 擴(kuò)展實(shí)現(xiàn)
基于 MyBatis 的增強(qiáng)工具,提供通用 Mapper(BaseMapper)和 ActiveRecord 模式,減少手寫 SQL。
// 繼承 BaseMapper 直接調(diào)用內(nèi)置方法
public interface UserMapper extends BaseMapper<User> {}
// 使用 ActiveRecord 模式
User user = new User();
user.setName("John");
user.insert();總結(jié)

3、緩存
緩存的查找順序:二級(jí)緩存 => 一級(jí)緩存 => 數(shù)據(jù)庫。

3.1. 一級(jí)緩存
一級(jí)緩存是SqlSession級(jí)別的緩存,默認(rèn)開啟,默認(rèn)是 SESSION 級(jí)

1.原理
每個(gè)SqlSession中持有了Executor,每個(gè)Executor中有一個(gè)LocalCache。
當(dāng)用戶發(fā)起查詢時(shí),MyBatis根據(jù)當(dāng)前執(zhí)行的語句生成MappedStatement,在Local Cache進(jìn)行查詢,如果緩存命中的話,直接返回結(jié)果給用戶,如果緩存沒有命中的話,查詢數(shù)據(jù)庫,結(jié)果寫入Local Cache,最后返回結(jié)果給用戶。
Local Cache 其實(shí)是一個(gè) hashmap 的結(jié)構(gòu):
private Map<Object, Object> cache = new HashMap<Object, Object>();

2.核心流程
- 第一次查詢:從數(shù)據(jù)庫讀取數(shù)據(jù),存入一級(jí)緩存。
- 第二次相同查詢:直接讀取緩存,不訪問數(shù)據(jù)庫。
- 執(zhí)行更新操作:清空一級(jí)緩存,后續(xù)查詢重新訪問數(shù)據(jù)庫。
代碼示例:
// 示例代碼
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); // 查詢數(shù)據(jù)庫
System.out.println("查詢結(jié)果: " + user1);
System.out.println("--- 第二次相同查詢 ---");
User user2 = mapper.getUserById(1L); // 從一級(jí)緩存讀取
System.out.println("查詢結(jié)果: " + user2);
System.out.println("user1 == user2? " + (user1 == user2)); // 輸出 true
System.out.println("--- 執(zhí)行更新操作 ---");
user1.setName("NewName");
mapper.updateUser(user1); // 清空一級(jí)緩存
session.commit(); // 提交事務(wù)
System.out.println("--- 第三次查詢 ---");
User user3 = mapper.getUserById(1L); // 重新查詢數(shù)據(jù)庫
System.out.println("查詢結(jié)果: " + user3);
}
}預(yù)期輸出:
--- 第一次查詢 ---
DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Long)
DEBUG [main] - <== Total: 1
查詢結(jié)果: User(id=1, name=OldName)--- 第二次相同查詢 ---
查詢結(jié)果: User(id=1, name=OldName)
user1 == user2? true // 直接從緩存獲取,無 SQL 日志--- 執(zhí)行更新操作 ---
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
查詢結(jié)果: User(id=1, name=NewName)
關(guān)鍵點(diǎn)
- 兩次查詢結(jié)果相同:第二次未觸發(fā) SQL,直接返回緩存對(duì)象(
user1 == user2)。 - 更新操作后緩存失效:第三次查詢重新訪問數(shù)據(jù)庫。
3.緩存失效
不同的SqlSession對(duì)應(yīng)不同的一級(jí)緩存
- 同一個(gè)SqlSession但是查詢條件不同
- 同一個(gè)SqlSession兩次查詢期間執(zhí)行了任何一次增刪改操作
- 同一個(gè)SqlSession兩次查詢期間手動(dòng)清空了緩存
3.2. 二級(jí)緩存
二級(jí)緩存是NameSpace級(jí)別(Mapper)的緩存,多個(gè)SqlSession可以共享,使用時(shí)需要進(jìn)行配置開啟,同一個(gè)namespace影響同一個(gè)cache。

1.執(zhí)行順序
- 先查詢二級(jí)緩存,因?yàn)槎?jí)緩存中可能會(huì)有其他程序已經(jīng)查出來的數(shù)據(jù),可以拿來直接使用。
- 如果二級(jí)緩存沒有命中,再查詢一級(jí)緩存
- 如果一級(jí)緩存也沒有命中,則查詢數(shù)據(jù)庫
- SqlSession關(guān)閉之后,一級(jí)緩存中的數(shù)據(jù)會(huì)寫入二級(jí)緩存。
代碼示例:
public static void main(String[] args) {
SqlSessionFactory factory = ...; // 已開啟二級(jí)緩存
// 第一個(gè)會(huì)話(查詢并提交)
try (SqlSession session1 = factory.openSession()) {
UserMapper mapper1 = session1.getMapper(UserMapper.class);
System.out.println("--- SqlSession1 查詢 ---");
User user1 = mapper1.getUserById(1L); // 查詢數(shù)據(jù)庫
System.out.println("查詢結(jié)果: " + user1);
session1.commit(); // 提交后同步到二級(jí)緩存
}
// 第二個(gè)會(huì)話(相同查詢)
try (SqlSession session2 = factory.openSession()) {
UserMapper mapper2 = session2.getMapper(UserMapper.class);
System.out.println("--- SqlSession2 查詢 ---");
User user2 = mapper2.getUserById(1L); // 從二級(jí)緩存讀取
System.out.println("查詢結(jié)果: " + user2);
}
// 第三個(gè)會(huì)話(更新數(shù)據(jù))
try (SqlSession session3 = factory.openSession()) {
UserMapper mapper3 = session3.getMapper(UserMapper.class);
System.out.println("--- SqlSession3 更新 ---");
User user = new User(1L, "UpdatedName");
mapper3.updateUser(user); // 清空二級(jí)緩存
session3.commit();
}
// 第四個(gè)會(huì)話(重新查詢)
try (SqlSession session4 = factory.openSession()) {
UserMapper mapper4 = session4.getMapper(UserMapper.class);
System.out.println("--- SqlSession4 查詢 ---");
User user4 = mapper4.getUserById(1L); // 重新查詢數(shù)據(jù)庫
System.out.println("查詢結(jié)果: " + user4);
}
}預(yù)期輸出:
--- SqlSession1 查詢 ---
DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Long)
DEBUG [main] - <== Total: 1
查詢結(jié)果: User(id=1, name=OldName)--- SqlSession2 查詢 ---
查詢結(jié)果: User(id=1, name=OldName) // 無 SQL 日志,直接讀二級(jí)緩存--- 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
查詢結(jié)果: User(id=1, name=UpdatedName)
關(guān)鍵點(diǎn)
- 跨會(huì)話共享緩存:
SqlSession2未訪問數(shù)據(jù)庫。 - 更新操作清空緩存:
SqlSession3更新后,SqlSession4重新查詢數(shù)據(jù)庫。
2、配置二級(jí)緩存(關(guān)鍵步驟)
1.實(shí)體類實(shí)現(xiàn) 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. 緩存區(qū)別

4、Sql注入
4.1.#{}參數(shù)占位符
#{} 是 MyBatis 中的 參數(shù)占位符,用于將參數(shù)傳遞到 SQL 語句中。 會(huì)將 #{} 中的參數(shù)值自動(dòng)進(jìn)行轉(zhuǎn)義,將 sql 中的#{}替換為 ? 號(hào)。
作用是將參數(shù)值進(jìn)行處理并填充到 SQL 語句中,在傳遞參數(shù)時(shí)會(huì)進(jìn)行預(yù)處理,避免 SQL 注入風(fēng)險(xiǎn)。
4.2.${}字符串拼接
${} 是 MyBatis 中的 字符串拼接占位符,用于直接將參數(shù)值拼接到 SQL 語句中。
它會(huì)將傳入的參數(shù)值直接替換到 SQL 中的 ${} 所在的位置,而不會(huì)進(jìn)行任何的處理或轉(zhuǎn)義。
由下圖所說:

顯然#{}直接去數(shù)據(jù)庫查詢,是查不到這個(gè)username,而${}由于沒有進(jìn)行轉(zhuǎn)義處理,就會(huì)執(zhí)行1=1;
4.3.SQL注入
MyBatis的#{}之所以能夠預(yù)防SQL注入是因?yàn)榈讓邮褂昧薖reparedStatement類的setString()方法來設(shè)置參數(shù),此方法會(huì)獲取傳遞進(jìn)來的參數(shù)的每個(gè)字符,然后進(jìn)行循環(huán)對(duì)比,如果發(fā)現(xiàn)有敏感字符(如:?jiǎn)我?hào)、雙引號(hào)等),則會(huì)在前面加上一個(gè)'/'代表轉(zhuǎn)義此符號(hào),讓其變?yōu)橐粋€(gè)普通的字符串,不參與SQL語句的生成,達(dá)到防止SQL注入的效果。
其次${}本身設(shè)計(jì)的初衷就是為了參與SQL語句的語法生成,自然而然會(huì)導(dǎo)致SQL注入的問題(不會(huì)考慮字符過濾問題)。
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) { //遍歷字符串,獲取到每個(gè)字符
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.用法總結(jié)
1)#{}在使用時(shí),會(huì)根據(jù)傳遞進(jìn)來的值來選擇是否加上雙引號(hào),因此我們傳遞參數(shù)的時(shí)候一般都是直接傳遞,不用加雙引號(hào),${}則不會(huì),我們需要手動(dòng)加。
2)在傳遞一個(gè)參數(shù)時(shí),我們說了#{}中可以寫任意的值,${}則必須使用value;即:${value}
3)#{}針對(duì)SQL注入進(jìn)行了字符過濾,${}則只是作為普通傳值,并沒有考慮到這些問題
4)#{}的應(yīng)用場(chǎng)景是為給SQL語句的where字句傳遞條件值,${}的應(yīng)用場(chǎng)景是為了傳遞一些需要參與SQL語句語法生成的值。
5、懶加載
MyBatis 的懶加載功能允許在需要時(shí)才加載關(guān)聯(lián)的對(duì)象,從而提高系統(tǒng)的性能。懶加載是一種常用的策略,尤其適用于處理大型數(shù)據(jù)集時(shí),避免在查詢時(shí)加載不必要的數(shù)據(jù)。
5.1、原理
在 MyBatis 中,懶加載是通過延遲加載關(guān)聯(lián)對(duì)象的策略實(shí)現(xiàn)的。當(dāng)你訪問一個(gè)未初始化的屬性時(shí),MyBatis 會(huì)自動(dòng)從數(shù)據(jù)庫加載相關(guān)的數(shù)據(jù),而不是在首次加載父對(duì)象時(shí)就將所有的關(guān)聯(lián)數(shù)據(jù)一起加載。
5.2、開啟
全局配置: 你可以在 MyBatis 的全局配置文件 mybatis-config.xml 中設(shè)置懶加載功能。
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/> <!-- 控制是否在訪問懶加載對(duì)象時(shí)立即加載 -->
</settings>
</configuration>lazyLoadingEnabled: 設(shè)置為true時(shí)啟用懶加載。aggressiveLazyLoading: 如果設(shè)置為true,當(dāng)你訪問一個(gè)對(duì)象的任何屬性時(shí),會(huì)立即加載所有懶加載的屬性。如果設(shè)置為false,那么只有在訪問特定的懶加載屬性時(shí)才會(huì)加載。

5.3、實(shí)現(xiàn)
假設(shè)我們有兩個(gè)實(shí)體:User 和 Order,每個(gè)用戶可以有多個(gè)訂單。這是一個(gè)常見的一對(duì)多關(guān)系的例子。
1. 數(shù)據(jù)庫表結(jié)構(gòu)
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; // 一對(duì)多關(guān)聯(lián)
// Getters 和 Setters
// ...
}Order.java
public class Order {
private Integer orderId;
private String orderNumber;
// Getters 和 Setters
// ...
}3. Mapper 接口
我們創(chuàng)建一個(gè) 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 映射文件
為了懶加載訂單數(shù)據(jù),我們可以在 User 類中定義一個(gè)方法來獲取訂單。如果你不希望在獲取用戶時(shí)立即加載用戶的所有訂單數(shù)據(jù),可以通過以下方式進(jìn)行設(shè)計(jì)。
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>標(biāo)簽表示與Order的一對(duì)多關(guān)系的懶加載配置。通過select屬性指定查詢訂單的 SQL。 fetchType="lazy"表示此集合數(shù)據(jù)將延遲加載。
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. 使用懶加載的情況
在你的服務(wù)層中,你可以調(diào)用 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 還沒有被加載,直到你調(diào)用 user.getOrders()
return user;
}
}6. 控制器層
你可以使用 Spring MVC 來創(chuàng)建一個(gè)控制器,處理 HTTP 請(qǐng)求。
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. 使用示例
當(dāng)你訪問 http://localhost:8080/users/1,會(huì)調(diào)用 getUser() 方法。
- 一開始,
findUserById方法會(huì)查詢用戶信息,但訂單還不會(huì)被加載。 - 當(dāng)你訪問
user.getOrders()時(shí),MyBatis 會(huì)自動(dòng)調(diào)用OrderMapper.findOrdersByUserId方法來查詢與該用戶相關(guān)的訂單。
8. 總結(jié)
以上代碼演示了如何在 MyBatis 中實(shí)現(xiàn)懶加載功能。通過配置 XML 文件中的 <collection> 標(biāo)簽結(jié)合 fetchType="lazy" 設(shè)置,可以確保在訪問相關(guān)集合屬性時(shí)才加載數(shù)據(jù)。這能幫助提高性能,減少不必要的數(shù)據(jù)加載。
懶加載機(jī)制在處理大數(shù)據(jù)量、頻繁不使用的關(guān)聯(lián)對(duì)象時(shí)尤為重要,有效節(jié)約系統(tǒng)資源。
6、常用問題
6.1、映射不一致
當(dāng)實(shí)體類中的屬性名和表中的字段名不一樣 ,怎么辦 ?
使用 @Results 注解或者 <resultMap> 來明確映射關(guān)系。
假設(shè)我們有一個(gè)數(shù)據(jù)庫表 users,其結(jié)構(gòu)如下:

在項(xiàng)目中,有一個(gè)對(duì)應(yīng)的 POJO 類 User,其屬性使用駝峰命名風(fēng)格:
public class User {
private Integer id; // 對(duì)應(yīng) user_id
private String username; // 對(duì)應(yīng) user_name
private String email; // 對(duì)應(yīng) user_email
get set方法略
}1、使用 XML(resultMap)
1. 創(chuàng)建 MyBatis 映射文件
將使用 XML 文件 UserMapper.xml 來定義 SQL 操作和字段映射。這個(gè)文件通常位于 resources 目錄下。
UserMapper.xml:
<mapper namespace="com.example.mapper.UserMapper">
<!-- 定義結(jié)果映射 -->
<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 注解來實(shí)現(xiàn)。下面是一個(gè)示例:
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();
// 創(chuàng)建一個(gè) User 對(duì)象
User user = new User();
user.setId(1);
user.setUsername("alice");
user.setEmail("alice@example.com");
// 將 User 對(duì)象序列化為 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 屬性之間的映射關(guān)系。
6.2、模糊查詢
1.CONCAT
創(chuàng)建一個(gè) Mapper 接口,并添加一個(gè)模糊查詢的方法。
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 關(guān)鍵字進(jìn)行模糊匹配。
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.傳遞參數(shù)%value%
在傳入?yún)?shù)的時(shí)候比如username:%value% 傳入到mapper文件里面;
6.3、動(dòng)態(tài)標(biāo)簽
1. <if>
用于判斷條件,如果條件滿足則會(huì)生成 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>在這個(gè)例子中,只有當(dāng) username 或 email 不為 null 時(shí),相應(yīng)的條件才會(huì)被添加到 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>在這個(gè)例子中,SQL 查詢將會(huì)根據(jù) status 的值選擇生成查詢條件。
3.<foreach>
用于循環(huán)遍歷集合,通常用于生成 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 語句前后可能出現(xiàn)的多余的字符,主要是在構(gòu)建可選條件時(shí)使用,查詢參數(shù)可以是可選的。
<select id="findUsersByCondition" resultType="User">
SELECT * 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> 可以去掉多余的前綴,比如如果沒有設(shè)定 username 和 email,可以避免生成 WHERE 后面緊跟著的 AND。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
推薦一款I(lǐng)ntelliJ IDEA提示快捷鍵的Key Promoter X插件
今天小編就為大家分享一篇關(guān)于IntelliJ IDEA提示快捷鍵的Key Promoter X插件,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-10-10
SpringBoot集成Sharding Jdbc使用復(fù)合分片的實(shí)踐
數(shù)據(jù)庫分庫分表中間件是采用的 apache sharding。本文主要介紹了SpringBoot集成Sharding Jdbc使用復(fù)合分片的實(shí)踐,具有一定的參考價(jià)值,感興趣的可以了解一下2021-09-09
redis防止重復(fù)提交的實(shí)現(xiàn)示例
在開發(fā)中我們都需要處理重復(fù)提交的問題,本文主要介紹了redis防止重復(fù)提交的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-06-06
使用postman傳遞list集合后臺(tái)springmvc接收
這篇文章主要介紹了使用postman傳遞list集合后臺(tái)springmvc接收的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
MyBatis 中 ${}和 #{}的正確使用方法(千萬不要亂用)
這篇文章主要介紹了MyBatis 中 ${}和 #{}的正確使用方法,本文給大家提到了MyBatis 中 ${}和 #{}的區(qū)別,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07
SpringBoot項(xiàng)目運(yùn)行一段時(shí)間后自動(dòng)關(guān)閉的坑及解決
這篇文章主要介紹了SpringBoot項(xiàng)目運(yùn)行一段時(shí)間后自動(dòng)關(guān)閉的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
SpringBoot靜態(tài)資源css,js,img配置方案
這篇文章主要介紹了SpringBoot靜態(tài)資源css,js,img配置方案,下文給大家分享了三種解決方案,需要的朋友可以參考下2017-07-07

