欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

關(guān)于Mybatis和JDBC的使用及區(qū)別

 更新時(shí)間:2025年05月13日 11:38:55   作者:找不到、了  
這篇文章主要介紹了關(guān)于Mybatis和JDBC的使用及區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

基于java的持久層框架,它內(nèi)部封裝了jdbc,使開(kāi)發(fā)者只需要關(guān)注sql語(yǔ)句本身,而不需要花費(fèi)精力去處理加載驅(qū)動(dòng)、創(chuàng)建連接、創(chuàng)建statement等繁雜的過(guò)程。

1、JDBC

1.1、流程

下面是一個(gè)簡(jiǎn)單的示例,演示如何使用 JDBC 直接與數(shù)據(jù)庫(kù)進(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ù)庫(kù)驅(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ù)庫(kù)驅(qū)動(dòng)
            Class.forName("com.mysql.cj.jdbc.Driver"); // 替換為相應(yīng)驅(qū)動(dòng)

            // 2. 建立數(shù)據(jù)庫(kù)連接
            String url = "jdbc:mysql://localhost:3306/yourdatabase"; // 數(shù)據(jù)庫(kù) URL
            String user = "yourusername"; // 數(shù)據(jù)庫(kù)用戶名
            String password = "yourpassword"; // 數(shù)據(jù)庫(kù)密碼
            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的流程:

  1. 加載數(shù)據(jù)庫(kù)驅(qū)動(dòng):加載與所使用數(shù)據(jù)庫(kù)對(duì)應(yīng)的 JDBC 驅(qū)動(dòng)。
  2. 建立數(shù)據(jù)庫(kù)連接:使用 DriverManager.getConnection() 方法建立與數(shù)據(jù)庫(kù)的連接。
  3. 創(chuàng)建 Statement PreparedStatement:用于執(zhí)行 SQL 查詢或更新。
  4. 執(zhí)行 SQL 語(yǔ)句:通過(guò) executeQuery()executeUpdate() 方法執(zhí)行 SQL 語(yǔ)句。
  5. 處理結(jié)果:處理查詢結(jié)果 (如果有)。
  6. 關(guān)閉資源:關(guān)閉 ResultSet、Statement、Connection 等資源,防止資源泄漏。

1.2、優(yōu)缺點(diǎn)

1.優(yōu)點(diǎn):

  • 直接靈活:可以完全控制 SQL 語(yǔ)句和數(shù)據(jù)庫(kù)交互的細(xì)節(jié)。
  • 簡(jiǎn)單:適合小型應(yīng)用或簡(jiǎn)單查詢。

2.缺點(diǎn):

  • 冗長(zhǎng):代碼冗長(zhǎng),尤其是在處理事務(wù)和異常時(shí),容易導(dǎo)致代碼重復(fù)。
  • 不易維護(hù):SQL 語(yǔ)句與業(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ù)庫(kù)的完整示例。這個(gè)示例展示了如何使用 MyBatis 進(jìn)行基本的 CRUD 操作,同時(shí)使用 MySQL 作為數(shù)據(jù)庫(kù)。

項(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); // 分頁(yè)查詢
    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} <!-- 分頁(yè)查詢 -->
    </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ù)類來(lái)處理業(yè)務(wù)邏輯,包括分頁(yè)查詢:

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)用分頁(yè)方法
        } finally {
            sqlSession.close(); // 關(guān)閉 SQL 會(huì)話
        }
    }
}

調(diào)用分頁(yè)查詢方法并打印結(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)行分頁(yè)查詢
        int pageNumber = 1; // 頁(yè)碼
        int pageSize = 5;   // 每頁(yè)條數(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ù)庫(kù)和表

在 MySQL 數(shù)據(jù)庫(kù)中,您需要?jiǎng)?chuàng)建數(shù)據(jù)庫(kù)和用戶表。假設(shè)您創(chuàng)建了一個(gè)名為 yourdatabase 的數(shù)據(jù)庫(kù),執(zhí)行以下 SQL 語(yǔ)句:

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配置文件

通過(guò) .xml 文件定義 SQL 語(yǔ)句和映射關(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 支持較弱,但通過(guò) @SelectProvider@UpdateProvider 或者xml方式等注解,現(xiàn)已能實(shí)現(xiàn)復(fù)雜邏輯。

XML 動(dòng)態(tài)標(biāo)簽

通過(guò) 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ù)庫(kù)。

3.1. 一級(jí)緩存

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

1.原理

每個(gè)SqlSession中持有了Executor,每個(gè)Executor中有一個(gè)LocalCache。

當(dāng)用戶發(fā)起查詢時(shí),MyBatis根據(jù)當(dāng)前執(zhí)行的語(yǔ)句生成MappedStatement,在Local Cache進(jìn)行查詢,如果緩存命中的話,直接返回結(jié)果給用戶,如果緩存沒(méi)有命中的話,查詢數(shù)據(jù)庫(kù),結(jié)果寫入Local Cache,最后返回結(jié)果給用戶。

Local Cache 其實(shí)是一個(gè) hashmap 的結(jié)構(gòu):

private Map<Object, Object> cache = new HashMap<Object, Object>();

2.核心流程

  1. 第一次查詢:從數(shù)據(jù)庫(kù)讀取數(shù)據(jù),存入一級(jí)緩存。
  2. 第二次相同查詢:直接讀取緩存,不訪問(wèn)數(shù)據(jù)庫(kù)。
  3. 執(zhí)行更新操作:清空一級(jí)緩存,后續(xù)查詢重新訪問(wèn)數(shù)據(jù)庫(kù)。

代碼示例:

// 示例代碼
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ù)庫(kù)
        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ù)庫(kù)
        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  // 直接從緩存獲取,無(wú) 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)。
  • 更新操作后緩存失效:第三次查詢重新訪問(wèn)數(shù)據(jù)庫(kù)。

3.緩存失效

  1. 不同的SqlSession對(duì)應(yīng)不同的一級(jí)緩存

  2. 同一個(gè)SqlSession但是查詢條件不同
  3. 同一個(gè)SqlSession兩次查詢期間執(zhí)行了任何一次增刪改操作
  4. 同一個(gè)SqlSession兩次查詢期間手動(dòng)清空了緩存

3.2. 二級(jí)緩存

二級(jí)緩存是NameSpace級(jí)別(Mapper)的緩存,多個(gè)SqlSession可以共享,使用時(shí)需要進(jìn)行配置開(kāi)啟,同一個(gè)namespace影響同一個(gè)cache。

1.執(zhí)行順序

  • 先查詢二級(jí)緩存,因?yàn)槎?jí)緩存中可能會(huì)有其他程序已經(jīng)查出來(lái)的數(shù)據(jù),可以拿來(lái)直接使用。
  • 如果二級(jí)緩存沒(méi)有命中,再查詢一級(jí)緩存
  • 如果一級(jí)緩存也沒(méi)有命中,則查詢數(shù)據(jù)庫(kù)
  • SqlSession關(guān)閉之后,一級(jí)緩存中的數(shù)據(jù)會(huì)寫入二級(jí)緩存。

代碼示例:

public static void main(String[] args) {
    SqlSessionFactory factory = ...; // 已開(kāi)啟二級(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ù)庫(kù)
        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ù)庫(kù)
        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)  // 無(wú) 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 未訪問(wèn)數(shù)據(jù)庫(kù)。
  • 更新操作清空緩存SqlSession3 更新后,SqlSession4 重新查詢數(shù)據(jù)庫(kù)。

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 語(yǔ)句中。 會(huì)將 #{} 中的參數(shù)值自動(dòng)進(jìn)行轉(zhuǎn)義,將 sql 中的#{}替換為 ? 號(hào)。

作用是將參數(shù)值進(jìn)行處理并填充到 SQL 語(yǔ)句中,在傳遞參數(shù)時(shí)會(huì)進(jìn)行預(yù)處理,避免 SQL 注入風(fēng)險(xiǎn)。

4.2.${}字符串拼接

${} 是 MyBatis 中的 字符串拼接占位符,用于直接將參數(shù)值拼接到 SQL 語(yǔ)句中。

它會(huì)將傳入的參數(shù)值直接替換到 SQL 中的 ${} 所在的位置,而不會(huì)進(jìn)行任何的處理或轉(zhuǎn)義。

由下圖所說(shuō):

顯然#{}直接去數(shù)據(jù)庫(kù)查詢,是查不到這個(gè)username,而${}由于沒(méi)有進(jìn)行轉(zhuǎn)義處理,就會(huì)執(zhí)行1=1;

4.3.SQL注入

MyBatis的#{}之所以能夠預(yù)防SQL注入是因?yàn)榈讓邮褂昧薖reparedStatement類的setString()方法來(lái)設(shè)置參數(shù),此方法會(huì)獲取傳遞進(jìn)來(lái)的參數(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語(yǔ)句的生成,達(dá)到防止SQL注入的效果。

其次${}本身設(shè)計(jì)的初衷就是為了參與SQL語(yǔ)句的語(yǔ)法生成,自然而然會(huì)導(dǎo)致SQL注入的問(wèn)題(不會(huì)考慮字符過(guò)濾問(wèn)題)。

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)來(lái)的值來(lái)選擇是否加上雙引號(hào),因此我們傳遞參數(shù)的時(shí)候一般都是直接傳遞,不用加雙引號(hào),${}則不會(huì),我們需要手動(dòng)加。

2)在傳遞一個(gè)參數(shù)時(shí),我們說(shuō)了#{}中可以寫任意的值,${}則必須使用value;即:${value}

3)#{}針對(duì)SQL注入進(jìn)行了字符過(guò)濾,${}則只是作為普通傳值,并沒(méi)有考慮到這些問(wèn)題

4)#{}的應(yīng)用場(chǎng)景是為給SQL語(yǔ)句的where字句傳遞條件值,${}的應(yīng)用場(chǎng)景是為了傳遞一些需要參與SQL語(yǔ)句語(yǔ)法生成的值。

5、懶加載

MyBatis 的懶加載功能允許在需要時(shí)才加載關(guān)聯(lián)的對(duì)象,從而提高系統(tǒng)的性能。懶加載是一種常用的策略,尤其適用于處理大型數(shù)據(jù)集時(shí),避免在查詢時(shí)加載不必要的數(shù)據(jù)。

5.1、原理

在 MyBatis 中,懶加載是通過(guò)延遲加載關(guān)聯(lián)對(duì)象的策略實(shí)現(xiàn)的。當(dāng)你訪問(wèn)一個(gè)未初始化的屬性時(shí),MyBatis 會(huì)自動(dòng)從數(shù)據(jù)庫(kù)加載相關(guān)的數(shù)據(jù),而不是在首次加載父對(duì)象時(shí)就將所有的關(guān)聯(lián)數(shù)據(jù)一起加載。

5.2、開(kāi)啟

全局配置: 你可以在 MyBatis 的全局配置文件 mybatis-config.xml 中設(shè)置懶加載功能。

<configuration>
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/> <!-- 控制是否在訪問(wèn)懶加載對(duì)象時(shí)立即加載 -->
    </settings>
</configuration>
  • lazyLoadingEnabled: 設(shè)置為 true 時(shí)啟用懶加載。
  • aggressiveLazyLoading: 如果設(shè)置為 true,當(dāng)你訪問(wèn)一個(gè)對(duì)象的任何屬性時(shí),會(huì)立即加載所有懶加載的屬性。如果設(shè)置為 false,那么只有在訪問(wèn)特定的懶加載屬性時(shí)才會(huì)加載。

5.3、實(shí)現(xiàn)

假設(shè)我們有兩個(gè)實(shí)體:UserOrder,每個(gè)用戶可以有多個(gè)訂單。這是一個(gè)常見(jiàn)的一對(duì)多關(guān)系的例子。

1. 數(shù)據(jù)庫(kù)表結(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 接口來(lái)定義查詢方法。

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è)方法來(lái)獲取訂單。如果你不希望在獲取用戶時(shí)立即加載用戶的所有訂單數(shù)據(jù),可以通過(guò)以下方式進(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)系的懶加載配置。通過(guò) 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 還沒(méi)有被加載,直到你調(diào)用 user.getOrders()
        return user;
    }
}

6. 控制器層

你可以使用 Spring MVC 來(lái)創(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)你訪問(wèn) http://localhost:8080/users/1,會(huì)調(diào)用 getUser() 方法。

  1. 一開(kāi)始,findUserById 方法會(huì)查詢用戶信息,但訂單還不會(huì)被加載。
  2. 當(dāng)你訪問(wèn) user.getOrders() 時(shí),MyBatis 會(huì)自動(dòng)調(diào)用 OrderMapper.findOrdersByUserId 方法來(lái)查詢與該用戶相關(guān)的訂單。

8. 總結(jié)

以上代碼演示了如何在 MyBatis 中實(shí)現(xiàn)懶加載功能。通過(guò)配置 XML 文件中的 <collection> 標(biāo)簽結(jié)合 fetchType="lazy" 設(shè)置,可以確保在訪問(wèn)相關(guān)集合屬性時(shí)才加載數(shù)據(jù)。這能幫助提高性能,減少不必要的數(shù)據(jù)加載。

懶加載機(jī)制在處理大數(shù)據(jù)量、頻繁不使用的關(guān)聯(lián)對(duì)象時(shí)尤為重要,有效節(jié)約系統(tǒng)資源。

6、常用問(wèn)題

6.1、映射不一致

當(dāng)實(shí)體類中的屬性名和表中的字段名不一樣 ,怎么辦 ?

使用 @Results 注解或者 <resultMap> 來(lái)明確映射關(guān)系。

假設(shè)我們有一個(gè)數(shù)據(jù)庫(kù)表 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 來(lái)定義 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 注解來(lái)實(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 注解幫助在序列化和反序列化過(guò)程中,指定 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 語(yǔ)句。我們將使用 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 語(yǔ)句中的代碼。

示例:

<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) usernameemail 不為 null 時(shí),相應(yīng)的條件才會(huì)被添加到 SQL 中。

2. <choose>、<when><otherwise>

用于多條件選擇的情況,類似于 Java 中的 switch-case 語(yǔ)句。

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 語(yǔ)句。

示例:

<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 語(yǔ)句前后可能出現(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> 可以去掉多余的前綴,比如如果沒(méi)有設(shè)定 usernameemail,可以避免生成 WHERE 后面緊跟著的 AND。

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論