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

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

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

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

1、JDBC

1.1、流程

下面是一個簡單的示例,演示如何使用 JDBC 直接與數(shù)據(jù)庫進(jìn)行交互:

1. Maven 依賴(如果使用 Maven)

如果您的項目使用 Maven,確保在 pom.xml 中包含 JDBC 驅(qū)動的依賴,例如使用 MySQL:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.23</version> <!-- 根據(jù)您需要的版本選擇 -->
</dependency>

2. 加載數(shù)據(jù)庫驅(qū)動和管理 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ū)動
            Class.forName("com.mysql.cj.jdbc.Driver"); // 替換為相應(yīng)驅(qū)動

            // 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的流程:

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

1.2、優(yōu)缺點

1.優(yōu)點:

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

2.缺點:

  • 冗長:代碼冗長,尤其是在處理事務(wù)和異常時,容易導(dǎo)致代碼重復(fù)。
  • 不易維護(hù):SQL 語句與業(yè)務(wù)邏輯混雜,導(dǎo)致難以維護(hù)和管理。
  • 手動管理:需要手動處理資源的關(guān)閉,容易出現(xiàn)資源泄漏。
  • 缺乏抽象:不支持對象關(guān)系映射,處理復(fù)雜數(shù)據(jù)結(jié)構(gòu)時不夠方便。

因此基于以上的現(xiàn)象,mybatis或者Hibernate就可以滿足更復(fù)雜的要求,且統(tǒng)一管理,便于維護(hù)。

2、Mybatis

2.1、執(zhí)行流程

2.2、使用

下面是一個使用 MyBatis 連接 MySQL 數(shù)據(jù)庫的完整示例。這個示例展示了如何使用 MyBatis 進(jìn)行基本的 CRUD 操作,同時使用 MySQL 作為數(shù)據(jù)庫。

項目結(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ū)動的依賴:

<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)建用戶實體類(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 實用工具類(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)建一個服務(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; // 計算偏移量
            return userMapper.getUsersByPage(offset, pageSize); // 調(diào)用分頁方法
        } finally {
            sqlSession.close(); // 關(guān)閉 SQL 會話
        }
    }
}

調(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ù)庫中,您需要創(chuàng)建數(shù)據(jù)庫和用戶表。假設(shè)您創(chuàng)建了一個名為 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、運行程序。

2.3、實現(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、動態(tài) SQL

早期 MyBatis 注解對動態(tài) SQL 支持較弱,但通過 @SelectProvider、@UpdateProvider 或者xml方式等注解,現(xiàn)已能實現(xiàn)復(fù)雜邏輯。

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

通過 XML 中的 <if>、<choose><foreach> 等標(biāo)簽實現(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)用工具類生成動態(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ò)展實現(xiàn)

基于 MyBatis 的增強工具,提供通用 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、緩存

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

3.1. 一級緩存

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

1.原理

每個SqlSession中持有了Executor,每個Executor中有一個LocalCache。

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

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

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

2.核心流程

  1. 第一次查詢:從數(shù)據(jù)庫讀取數(shù)據(jù),存入一級緩存。
  2. 第二次相同查詢:直接讀取緩存,不訪問數(shù)據(jù)庫。
  3. 執(zhí)行更新操作:清空一級緩存,后續(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); // 從一級緩存讀取
        System.out.println("查詢結(jié)果: " + user2);
        System.out.println("user1 == user2? " + (user1 == user2)); // 輸出 true
        
        System.out.println("--- 執(zhí)行更新操作 ---");
        user1.setName("NewName");
        mapper.updateUser(user1); // 清空一級緩存
        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)鍵點

  • 兩次查詢結(jié)果相同:第二次未觸發(fā) SQL,直接返回緩存對象(user1 == user2)。
  • 更新操作后緩存失效:第三次查詢重新訪問數(shù)據(jù)庫。

3.緩存失效

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

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

3.2. 二級緩存

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

1.執(zhí)行順序

  • 先查詢二級緩存,因為二級緩存中可能會有其他程序已經(jīng)查出來的數(shù)據(jù),可以拿來直接使用。
  • 如果二級緩存沒有命中,再查詢一級緩存
  • 如果一級緩存也沒有命中,則查詢數(shù)據(jù)庫
  • SqlSession關(guān)閉之后,一級緩存中的數(shù)據(jù)會寫入二級緩存。

代碼示例:

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); // 查詢數(shù)據(jù)庫
        System.out.println("查詢結(jié)果: " + 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("查詢結(jié)果: " + user2);
    }

    // 第三個會話(更新數(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); // 清空二級緩存
        session3.commit();
    }

    // 第四個會話(重新查詢)
    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 日志,直接讀二級緩存

--- 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)鍵點

  • 跨會話共享緩存SqlSession2 未訪問數(shù)據(jù)庫。
  • 更新操作清空緩存SqlSession3 更新后,SqlSession4 重新查詢數(shù)據(jù)庫。

2、配置二級緩存(關(guān)鍵步驟)

1.實體類實現(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 語句中。 會將 #{} 中的參數(shù)值自動進(jìn)行轉(zhuǎn)義,將 sql 中的#{}替換為 ? 號。

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

4.2.${}字符串拼接

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

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

由下圖所說:

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

4.3.SQL注入

MyBatis的#{}之所以能夠預(yù)防SQL注入是因為底層使用了PreparedStatement類的setString()方法來設(shè)置參數(shù),此方法會獲取傳遞進(jìn)來的參數(shù)的每個字符,然后進(jìn)行循環(huán)對比,如果發(fā)現(xiàn)有敏感字符(如:單引號、雙引號等),則會在前面加上一個'/'代表轉(zhuǎn)義此符號,讓其變?yōu)橐粋€普通的字符串,不參與SQL語句的生成,達(dá)到防止SQL注入的效果。

其次${}本身設(shè)計的初衷就是為了參與SQL語句的語法生成,自然而然會導(dǎo)致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.用法總結(jié)

1)#{}在使用時,會根據(jù)傳遞進(jìn)來的值來選擇是否加上雙引號,因此我們傳遞參數(shù)的時候一般都是直接傳遞,不用加雙引號,${}則不會,我們需要手動加。

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

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

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

5、懶加載

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

5.1、原理

在 MyBatis 中,懶加載是通過延遲加載關(guān)聯(lián)對象的策略實現(xiàn)的。當(dāng)你訪問一個未初始化的屬性時,MyBatis 會自動從數(shù)據(jù)庫加載相關(guān)的數(shù)據(jù),而不是在首次加載父對象時就將所有的關(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"/> <!-- 控制是否在訪問懶加載對象時立即加載 -->
    </settings>
</configuration>
  • lazyLoadingEnabled: 設(shè)置為 true 時啟用懶加載。
  • aggressiveLazyLoading: 如果設(shè)置為 true,當(dāng)你訪問一個對象的任何屬性時,會立即加載所有懶加載的屬性。如果設(shè)置為 false,那么只有在訪問特定的懶加載屬性時才會加載。

5.3、實現(xiàn)

假設(shè)我們有兩個實體:UserOrder,每個用戶可以有多個訂單。這是一個常見的一對多關(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; // 一對多關(guān)聯(lián)

    // Getters 和 Setters
    // ...
}

Order.java

public class Order {
    private Integer orderId;
    private String orderNumber;

    // Getters 和 Setters
    // ...
}

3. Mapper 接口

我們創(chuàng)建一個 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 類中定義一個方法來獲取訂單。如果你不希望在獲取用戶時立即加載用戶的所有訂單數(shù)據(jù),可以通過以下方式進(jìn)行設(shè)計。

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 的一對多關(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)建一個控制器,處理 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. 使用示例

當(dāng)你訪問 http://localhost:8080/users/1,會調(diào)用 getUser() 方法。

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

8. 總結(jié)

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

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

6、常用問題

6.1、映射不一致

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

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

假設(shè)我們有一個數(shù)據(jù)庫表 users,其結(jié)構(gòu)如下:

在項目中,有一個對應(yīng)的 POJO 類 User,其屬性使用駝峰命名風(fēng)格:

public class User {
    private Integer id;        // 對應(yīng) user_id
    private String username;   // 對應(yīng) user_name
    private String email;      // 對應(yīng) user_email

   get set方法略
}

1、使用 XML(resultMap)

1. 創(chuàng)建 MyBatis 映射文件

將使用 XML 文件 UserMapper.xml 來定義 SQL 操作和字段映射。這個文件通常位于 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 注解來實現(xiàn)。下面是一個示例:

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)建一個 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 屬性之間的映射關(guān)系。

6.2、模糊查詢

1.CONCAT

創(chuàng)建一個 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 關(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ù)的時候比如username:%value% 傳入到mapper文件里面;

6.3、動態(tài)標(biāo)簽

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>

在這個例子中,只有當(dāng) usernameemail 不為 null 時,相應(yīng)的條件才會被添加到 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 查詢將會根據(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ù)可以是可選的。

<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è)定 usernameemail,可以避免生成 WHERE 后面緊跟著的 AND。

總結(jié)

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

相關(guān)文章

  • 推薦一款I(lǐng)ntelliJ IDEA提示快捷鍵的Key Promoter X插件

    推薦一款I(lǐng)ntelliJ IDEA提示快捷鍵的Key Promoter X插件

    今天小編就為大家分享一篇關(guān)于IntelliJ IDEA提示快捷鍵的Key Promoter X插件,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-10-10
  • SpringBoot集成Sharding Jdbc使用復(fù)合分片的實踐

    SpringBoot集成Sharding Jdbc使用復(fù)合分片的實踐

    數(shù)據(jù)庫分庫分表中間件是采用的 apache sharding。本文主要介紹了SpringBoot集成Sharding Jdbc使用復(fù)合分片的實踐,具有一定的參考價值,感興趣的可以了解一下
    2021-09-09
  • redis防止重復(fù)提交的實現(xiàn)示例

    redis防止重復(fù)提交的實現(xiàn)示例

    在開發(fā)中我們都需要處理重復(fù)提交的問題,本文主要介紹了redis防止重復(fù)提交的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下
    2024-06-06
  • 使用postman傳遞list集合后臺springmvc接收

    使用postman傳遞list集合后臺springmvc接收

    這篇文章主要介紹了使用postman傳遞list集合后臺springmvc接收的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • MyBatis 中 ${}和 #{}的正確使用方法(千萬不要亂用)

    MyBatis 中 ${}和 #{}的正確使用方法(千萬不要亂用)

    這篇文章主要介紹了MyBatis 中 ${}和 #{}的正確使用方法,本文給大家提到了MyBatis 中 ${}和 #{}的區(qū)別,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • 一文秒懂idea的git插件跟翻譯插件

    一文秒懂idea的git插件跟翻譯插件

    idea之類的開發(fā)軟件真的超級多的插件,今天給大家分享idea的git插件跟翻譯插件,感興趣的朋友跟隨小編一起看看吧
    2021-04-04
  • SpringBoot項目運行一段時間后自動關(guān)閉的坑及解決

    SpringBoot項目運行一段時間后自動關(guān)閉的坑及解決

    這篇文章主要介紹了SpringBoot項目運行一段時間后自動關(guān)閉的坑及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Java中return的用法(兩種)

    Java中return的用法(兩種)

    這篇文章主要介紹了Java中return的用法(兩種)的相關(guān)資料,需要的朋友可以參考下
    2016-01-01
  • 詳解Java8 新特性之日期API

    詳解Java8 新特性之日期API

    Java 8 在包java.time下包含了一組全新的時間日期API。下面通過示例給大家講解java8 新特征日期api的相關(guān)知識,感興趣的朋友一起看看吧
    2017-07-07
  • SpringBoot靜態(tài)資源css,js,img配置方案

    SpringBoot靜態(tài)資源css,js,img配置方案

    這篇文章主要介紹了SpringBoot靜態(tài)資源css,js,img配置方案,下文給大家分享了三種解決方案,需要的朋友可以參考下
    2017-07-07

最新評論