关于Mybatis和JDBC的使用及区别

2025-05-14 02:50
文章标签 mybatis jdbc 区别 使用

本文主要是介绍关于Mybatis和JDBC的使用及区别,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《关于Mybatis和JDBC的使用及区别》:本文主要介绍关于Mybatis和JDBC的使用及区别,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教...

基于Java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。

1、JDBC

1.1、流程

下面是一个简单的示例,演示如何使用 JDBC 直接与数据库进行交互:

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

如果您的项目使用 Maven,确保在 pom.xml 中包含 JDBC 驱动的依赖,例如使用 mysql

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.23</version> <!-- 根据您需要的版本选择 -->
</dependency>

2. 加载数据库驱动和管理 SQL

import java.sql.*;

public class JdbcExample {

    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            // 1. 加载数据库驱动
            Class.forName("com.mysql.cj.jdbc.Driver"); // 替换为相应驱动

            // 2. 建立数据库连接
            String url = "jdbc:mysql://localhost:3306/yourdatabase"; // 数据库 URL
            String user = "yourusername"; // 数据库用户名
            String password = "yourpassword"; // 数据库密码
            connection = DriverManager.getConnection(url, user, password);

            // 3. 创建 PreparedStatement
            String sql = "SELECT * FROM users WHERE id = ?";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setLong(1, 1); // 设置参数

            // 4. 执行查询
            resultSet = preparedStatement.executeQuery();

            // 5. 处理结果
            while (resultSet.next()) {
                System.out.println("User ID: " + resultSet.getInt("id"));
                System.out.println("User Name: " + resultSet.getString("name"));
                System.out.println("User Email: " + resultSet.getString("email"));
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭资源
            try {
                if (resultSet != null) resultSet.close();
                if (preparedStatement != null) preparedStatement.close();
                if (connection != null) connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

从以上代码展示,可以看到jdbc的流程:

  1. 加载数据库驱动:加载与所使用数据库对应的 JDBC 驱动。
  2. 建立数据库连接:使用 DriverManager.getConnection() 方法建立与数据库的连接。
  3. 创建 Statement PreparedStatement:用于执行 SQL 查询或更新。
  4. 执行 SQL 语句:通过 executeQuery()executeUpdate() 方法执行 SQL 语句。
  5. 处理结果:处理查询结果 (如果有)。
  6. 关闭资源:关闭 ResultSetStatementConnection 等资源,防止资源泄漏。

1.2、优缺点

1.优点:

  • 直接灵活:可以完全控制 SQL 语句和数据库交互的细节。
  • 简单:适合小型应用或简单查询。

2.缺点:

  • 冗长:代码冗长,尤其是在处理事务和异常时,容易导致代码重复。
  • 不易维护:SQL 语句与业务逻辑混杂,导致难以维护和管理。
  • 手动管理:需要手动处理资源的关闭,容易出现资源泄漏。
  • 缺乏抽象:不支持对象关系映射,处理复杂数据结构时不够方便。

因此基于以上的现象,mybatis或者Hibernate就可以满足更复杂的要求,且统一管理,便于维护。

2、Mybatis

2.1、执行流程

关于Mybatis和JDBC的使用及区别

2.2、使用

下面是一个使用 MyBatis 连接 MySQL 数据库的完整示例。这个示例展示了如何使用 MyBatis 进行基本的 CRUD 操作,同时使用 MySQL 作为数据库。

项目结构:

mybatis-mysql-example

├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── example
│   │   │           ├── Main.java
│   │   │           ├── User.java
│   │   │           ├── UserMapper.java
│   │   │           └── MyBatisUtils.java
│   │   └── resources
│   │       ├── mybatis-config.xml
│   │       └── mapper
│   │           └── UserMapper.xml
└── pom.xml

1. Maven 依赖

确保在 pom.xml 中添加 MyBatis 和 MySQL 的 JDBC 驱动的依赖:

<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>mybatis-mysql-example</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <java.version>11</java.version>
        <mybatis.version>3.5.7</mybatis.version>
        <mysql.version>8.0.25</mysql.version> <!-- 使用适合你的版本 -->
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2. 创建用户实体类(User.java)

package com.example;

public class User {
    private Long id;
    private String username;
    private String email;

    // Getters 和 Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

3. 创建 Mapper 接口(UserMapper.java)

package com.example;

import org.apache.ibatis.annotations.*;

import java.util.List;

@Mapper
public interface UserMapper {
    void insertUser(User user);
    User getUser(Long id);
    List<User> getAllUsers();
    List<User> getUsersByPage(@Param("offset") int offset, @Param("limit") int limit); // 分页查询
    void updateUser(User user);
    void deleteUser(Long id);
}

4. 创建 MyBatis 配置文件(mybatis-config.xml)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org/DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/yourdatabase?useSSL=false&serverTimezone=UTC"/>
                <property name="username" value="yourusername"/>
                <property name="password" value="yourpassword"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

5. 创建 Mapper XML 文件(UserMapper.xml)

src/main/resources/mapper 目录下创建 UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.UserMapper">

    <insert id="insertUser" parameterType="com.example.User">
        INSERT INTO users (username, email) VALUES (#{username}, #{email})
    </insert>

    <select id="getUser" resultType="com.example.User" parameterType="long">
        SELECT * FROM users WHERE id = #{id}
    </select>

    <select id="getAllUsers" resultType="com.example.User">
        SELECT * FROM users
    </select>

    <select id="getUsersByPage" resultType="com.example.User">
        SELECT * FROM users LIMIT #{limit} OFFSET #{offset} <!-- 分页查询 -->
    </select>

    <update id="updateUser" parameterType="com.example.User">
        UPDATE users SET username = #{username}, email = #{email} WHERE id = #{id}
    </update>

    <delete id="deleteUser" parameterType="long">
        DELETE FROM users WHERE id = #{id}
    </delete>
</mapper>

6. 创建 MyBatis 实用工具类(MyBatisUtils.java)

package com.example;

import org.apache.ibatis.session.SqlSession;
import orgmgIpm.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;

public class MyBatisUtils {
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            // 读取 mybatis-config.xml 配置文件
            InputStream inputStream = MyBatisUtils.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}

7. 创建主类(Main.java)

这是程序的入口,用于执行 CRUD 操作:

您可以创建一个服务类来处理业务逻辑,包括分页查询:

package com.example;

import org.apache.ibatis.session.SqlSession;
import java.util.List;

public class UserService {
    public List<User> getUsersByPage(int pageNumber, int pageSize) {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            int offset = (pageNumber - 1) * pageSize; // 计算偏移量
            return userMapper.getUsersByPage(offset, pageSize); // 调用分页方法
        } finally {
            sqlSession.close(); // 关闭 SQL 会话
        }
    }
}

调用分页查询方法并打印结果:

package com.example;

import org.apache.ibatis.session.SqlSession;

import java.util.List;

public class Main {
    public static void main(String[] args) {
        // Initialize MyBatis and data
        UserService userService = new UserService();

        // 进行分页查询
        int pageNumber = 1; // 页码
        int pageSize = 5;   // 每页条数
      python  List<User> users = userService.getUsersByPage(pageNumber, pageSize);

        // 打印结果
        System.out.println("Users on Page " + pageNumber + ":");
        for (User user : users) {
            System.out.println("- " + user.getUsername());
        }
    }
}

其他接口可以使用以下方法查询:

package com.example;

import org.apache.ibatis.session.SqlSession;

import java.util.List;

public class Main {
    public static void main(String[] args) {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

            // 插入用户
            User user = new User();
            user.setUsername("john_doe");
            user.setEmail("john@example.com");
            userMapper.insertUser(user);
            sqlSession.commit();

            // 查询用户
            User retrievedUser = userMapper.getUser(1L);
            System.out.println("Retrieved User: " + retrievedUser.getUsername());

            // 查询所有用户
            List<User> users = userMapper.getAllUsers();
            System.out.println("All Users:");
            for (User u : users) {
                System.out.println(u.getUsername());
            }

            // 更新用户
            user.setEmail("john_doe_updated@example.com");
            userMapper.updateUser(user);
            sqlSession.commit();

            // 删除用户
            userMapper.deleteUser(1L);
            sqlSession.commit();
        } finally {
            sqlSession.close(); // 确保资源关闭
        }
    }
}

8. 创建 MySQL 数据库和表

在 MySQL 数据库中,您需要创建数据库和用户表。假设您创建了一个名为 yourdatabase 的数据库,执行以下 SQL 语句:

CREATE DATABASE yourdatabase;

USE yourdatabase;

CREATE TABLE users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(100),
    email VARCHAR(100)
);

9、运行程序。

2.3、实现方式

1、xml配置文件

通过 .xml 文件定义 SQL 语句和映射关系,与 Mapper 接口绑定。

<!-- UserMapper.xml -->
<select id="getUserById" resultType="User">
  SELECT * FROM user WHERE id = #{id}
</select>

2、注解

直接在 Mapper 接口的方法上使用注解(如 @Select@Update)编写 SQL。

@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(Long id);

3、动态 SQL

早期 MyBatis 注解对动态 SQL 支持较弱,但通过 @SelectProvider@UpdateProvider 或者xml方式等注解,现已能实现复杂逻辑。

XML 动态标签

通过 XML 中的 <if><choose><foreach> 等标签实现条件逻辑。

代码示例:

<select id="searchUsers">
  SELECT * FROM user
  <where>
    <if test="name != null">AND name = #{name}</if>
    <if test="role != null">AND role = #{role}</if>
  </where>
</select>

注解结合 Provider 类

使用 @SelectProvider@UpdateProvider 注解,调用工具类生成动态 SQL:

代码示例:

@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUserByName")
User getUserByName(@Param("name") String name);

class UserSqlBuilder {
    public String buildGetUserByName(Map<String, Object> params) {
        return new SQL() {{
            SELECT("*");
            FROM("user");
            WHERE("name = #{name}");
            if (params.get("role") != null) {
                AND().WHERE("role = #{role}");
            }
        }}.toString();
    }
}

4、MyBatis-Plus 扩展实现

基于 MyBatis 的增强工具,提供通用 Mapper(BaseMapper)和 ActiveRecord 模式,减少手写 SQL。

// 继承 BaseMapper 直接调用内置方法
public interface UserMapper extends BaseMapper<User> {}

// 使用 ActiveRecord 模式
User user = new User();
user.setName("John");
user.insert();

总结

关于Mybatis和JDBC的使用及区别

3、缓存

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

关于Mybatis和JDBC的使用及区别

3.1. 一级缓存

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

关于Mybatis和JDBC的使用及区别

1.原理

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

当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。

Local Cache 其实是一个 hashmap 的结构:

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

关于Mybatis和JDBC的使用及区别

2.核心流程

  1. 第一次查询:从数据库读取数据,存入一级缓存。
  2. 第二次相同查询:直接读取缓存,不访问数据库。
  3. 执行更新操作:清空一级缓存,后续查询重新访问数据库。

代码示例:

// 示例代码
public static void main(String[] args) {
    SqlSessionFactory factory = ...; // 获取 SqlSessionFactory

    try (SqlSession session = factory.openSession()) {
        UserMapper mapper = session.getMapper(UserMapper.class);
        
        System.out.println("--- 第一次查询 ---");
        User user1 = mapper.getUserById(1L); // 查询数据库
        System.out.println("查询结果: " + user1);
        
        System.out.println("--- 第二次相同查询 ---");
        User user2 = mapper.getUserById(1L); // 从一级缓存读取
        System.out.println("查询结果: " + user2);
        System.out.println("user1 == user2? " + (user1 == user2)); // 输出 true
        
        System.out.println("--- 执行更新操作 ---");
        user1.setName("NewName");
        mapper.updateUser(user1); // 清空一级缓存
        session.commit(); // 提交事务
        
        System.out.println("--- 第三次查询 ---");
        User user3 = mapper.getUserById(1L); // 重新查询数据库
        System.out.println("查询结果: " + user3);
    }
}

预期输出:

--- 第一次查询 ---
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Long)
DEBUG [main] - <==      Total: 1
查询结果: User(id=1, name=OldName)

--- 第二次相同查询 ---
查询结果: User(id=1, name=OldName)
user1 == user2? true  // 直接从缓存获取,无 SQL 日志

--- 执行更新操作 ---
DEBUG [main] - ==>  Preparing: UPDATE user SET name = ? WHERE id = ?
DEBUG [main] - ==> Parameters: NewName(String), 1(Long)
DEBUG [main] - <==    Updates: 1

--- 第三次查询 ---
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Long)
DEBUG [main] - <==      Total: 1
查询结果: User(id=1, name=NewName)

关键点

  • 两次查询结果相同:第二次未触发 SQL,直接返回缓存对象(user1 == user2)。
  • 更新操作后缓存失效:第三次查询重新访问数据库。

3.缓存失效

  1. 不同的SqlSession对应不同的一级缓存

  2. 同一个SqlSession但是查询条件不同
  3. 同一个SqlSession两次查询期间执行了任何一次增删改操作
  4. 同一个SqlSession两次查询期间手动清空了缓存

3.2. 二级缓存

二级缓存是NameSpace级别(Mapper)的缓存,多个SqlSession可以共享,使用时需要进行配置开启,同一个namespace影响同一个cache。

关于Mybatis和JDBC的使用及区别

1.执行顺序

  • 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
  • 如果二级缓存没有命中,再查询一级缓存
  • 如果一级缓存也没有命中,则查询数据库
  • SqlSession关闭之后,一级缓存中的数据会写入二级缓存。

代码示例:

public static void main(String[] args) {
    SqlSessionFactory factory = ...; // 已开启二级缓存

    // 第一个会话(查询并提交)
    try (SqlSession session1 = factory.openSession()) {
        UserMapper mapper1 = session1.getMapper(UserMapper.class);
        System.out.println("--- SqlSession1 查询 ---");
        User user1 = mapper1.getUserById(1L); // 查询数据库
        System.out.println("查询结果: " + user1);
        session1.commit(); // 提交后同步到二级缓存
    }

    // 第二个会话(相同查询)
    try (SqlSession session2 = factory.openSession()) {
        UserMapper mapper2 = session2.getMapper(UserMapper.class);
        System.out.println("--- SqlSession2 查询 ---");
        User user2 = mapper2.getUserById(1L); // 从二级缓存读取
        System.out.println("查询结果: " + user2);
    }

    // 第三个会话(更新数据)
    try (SqlSession session3 = factory.openSession()) {
        UserMapper mapper3 = session3.getMapper(UserMapper.class);
        System.out.println("--- SqlSession3 更新 ---");
        User user = new User(1L, "UpdatedName");
        mapper3.updateUser(user); // 清空二级缓存
        session3.commit();
    }

    // 第四个会话(重新查询)
    try (SqlSession session4 = factory.openSession()) {
        UserMapper mapper4 = session4.getMapper(UserMapper.class);
        System.out.println("--- SqlSession4 查询 ---");
        User user4 = mapper4.getUserById(1L); // 重新查询数据库
        System.out.println("查询结果: " + user4);
    }
}

预期输出:

--- SqlSession1 查询 ---
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE id = ?
DEBUG [main]China编程 - ==> Parameters: 1(Long)
DEBUG [main] - <==      Total: 1
查询结果: User(id=1, name=OldName)

--- SqlSession2 查询 ---
查询结果: User(id=1, name=OldName)  // 无 SQL 日志,直接读二级缓存

--- SqlSession3 更新 ---
DEBUG [main] - ==>  Preparing: UPDATE user SET name = ? WHERE id = ?
DEBUG [main] - ==> Parameters: UpdatedName(String), 1(Long)
DEBUG [main] - <==    Updates: 1

--- SqlSession4 查询 ---
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Long)
DEBUG [main] - <==      Total: 1
查询结果: User(id=1, name=UpdatedName)

关键点

  • 跨会话共享缓存SqlSession2 未访问数据库。
  • 更新操作清空缓存SqlSession3 更新后,SqlSession4 重新查询数据库。

2、配置二级缓存(关键步骤)

1.实体类实现 Serializable

public class User implements Serializable {
    // ...
}

2.Mapper XML 中添加 <cache/>

<mapper namespace="com.example.UserMapper">
    <cache eviction="LRU" flushInterval="60000" size="1024"/>
</mapper>

3.3. 缓存区别

关于Mybatis和JDBC的使用及区别

4、Sql注入

4.1.#{}参数占位符

#{} 是 MyBatis 中的 参数占位符,用于将参数传递到 SQL 语句中。 会将 #{} 中的参数值自动进行转义,将 sql 中的#{}替换为 ? 号。

作用是将参数值进行处理并填充到 SQL 语句中,在传递参数时会进行预处理,避免 SQL 注入风险。

4.2.${}字符串拼接

${} 是 MyBatis 中的 字符串拼接占位符,用于直接将参数值拼接到 SQL 语句中。

它会将传入的参数值直接替换到 SQL 中的 ${} 所在的位置,而不会进行任何的处理或转义。

由下图所说:

关于Mybatis和JDBC的使用及区别

显然#{}直接去数据库查询,是查不到这个username,而${}由于没有进行转义处理,就会执行1=1;

4.3.SQL注入

MyBatis的#{}之所以能够预防SQL注入是因为底层使用了PreparedStatement类的setString()方法来设置参数,此方法会获取传递进来的参数的每个字符,然后进行循环对比,如果发现有敏感字符(如:单引号、双引号等),则会在前面加上一个'/'代表转义此符号,让其变为一个普通的字符串,不参与SQL语句的生成,达到防止SQL注入的效果。

其次${}本身设计的初衷就是为了参与SQL语句的语法生成,自然而然会导致SQL注入的问题(不会考虑字符过滤问题)。

setString方法为什么可以解决。

public void setString(int parameterIndex, String x) throws SQLException {
        synchronized(this.checkClosed().getConnectionMutex()) {
            if (x == null) {
                this.setNull(parameterIndex, 1);
            } else {
                this.checkClosed();
                int stringLength = x.length();
                StringBuilder buf;
                if (this.connection.isNoBackslashEscapesSet()) {
                    boolean needsHexEscape = this.isEscapeNeededForString(x, stringLength);
                    Object parameterAsBytes;
                    byte[] parameterAsBytes;
                    if (!needsHexEscape) {
                        parameterAsBytes = null;
                        buf = new StringBuilder(x.length() + 2);
                        buf.append('\'');
                        buf.append(x);
                        buf.append('\'');
                        if (!this.isLoadDataQuery) {
                            parameterAsBytes = StringUtils.getBytes(buf.toString(), this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
                        } else {
                            parameterAsBytes = StringUtils.getBytes(buf.toString());
                        }

                        this.setInternal(parameterIndex, parameterAsBytes);
                    } else {
                        parameterAsBytes = null;
                        if (!this.isLoadDataQuery) {
                            parameterAsBytes = StringUtils.getBytes(x, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
                        } else {
                            parameterAsBytes = StringUtils.getBytes(x);
                        }

                        this.setBytes(parameterIndex, parameterAsBytes);
                    }

                    return;
                }

                String parameterAsString = x;
                boolean needsQuoted = true;
                if (this.isLoadDataQuery || this.isEscapeNeededForString(x, stringLength)) {
                    needsQuoted = false;
                    buf = new StringBuilder((int)((double)x.length() * 1.1D));
                    buf.append('\'');

                    for(int i = 0; i < stringLength; ++i) {		//遍历字符串,获取到每个字符
                        char c = x.charAt(i);
                        switch(c) {
                        case '\u0000':
                            buf.append('\\');
                            buf.append('0');
                            break;
                        case '\n':
                            buf.append('\\');
                            buf.append('n');
                            break;
                        case '\r':
                            buf.append('\\');
                            buf.append('r');
                            break;
                        case '\u001a':
                            buf.append('\\');
                            buf.append('Z');
                            break;
                        case '"':
                            if (this.usingAnsiMode) {
                                buf.append('\\');
                            }

                            buf.append('"');
                            break;
                        case '\'':
                            buf.append('\\');
                            buf.append('\'');
                            break;
                        case '\\':
                            buf.append('\\');
                            buf.append('\\');
                            break;
                        case '':
                        case '₩':
                            if (this.charsetEncoder != null) {
                                CharBuffer cbuf = CharBuffer.allocate(1);
                                ByteBuffer bbuf = ByteBuffer.allocate(1);
                                cbuf.put(c);
                                cbuf.position(0);
                                this.charsetEncoder.encode(cbuf, bbuf, true);
                                if (bbuf.get(0) == 92) {
                                    buf.append('\\');
                                }
                            }

                            buf.append(c);
                            break;
                        default:
                            buf.append(c);
                        }
                    }

                    buf.append('\'');
                    parameterAsString = buf.toString();
                }

                buf = null;
                byte[] parameterAsBytes;
                if (!this.isLoadDataQuery) {
                    if (needsQuoted) {
                        parameterAsBytes = StringUtils.getBytesWrapped(parameterAsString, '\'', '\'', this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
                    } else {
                        parameterAsBytes = StringUtils.getBytes(parameterAsString, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
                    }
                } else {
                    parameterAsBytes = StringUtils.getBytes(parameterAsString);
                }

                this.setInternal(parameterIndex, parameterAsBytes);
                this.parameterTypes[parameterIndex - 1 + this.getParameterIndexOffset()] = 12;
            }

        }
    }

4.4.用法总结

1)#{}在使用时,会根据传递进来的值来选择是否加上双引号,因此我们传递参数的时候一般都是直接传递,不用加双引号,${}则不会,我们需要手动加。

2)在传递一个参数时,我们说了#{}中可以写任意的值,${}则必须使用value;即:${value}

3)#{}针对SQL注入进行了字符过滤,${}则只是作为普通传值,并没有考虑到这些问题

4)#{}的应用场景是为给SQL语句的where字句传递条件值,${}的应用场景是为了传递一些需要参与SQL语句语法生成的值。

5、懒加载

MyBatis 的懒加载功能允许在需要时才加载关联的对象,从而提高系统的性能。懒加载是一种常用的策略,尤其适用于处理大型数据集时,避免在查询时加载不必要的数据。

5.1、原理

在 MyBatis 中,懒加载是通过延迟加载关联对象的策略实现的。当你访问一个未初始化的属性时,MyBatis 会自动从数据库加载相关的数据,而不是在首次加载父对象时就将所有的关联数据一起加载。

5.2、开启

全局配置: 你可以在 MyBatis 的全局配置文件 mybatis-config.xml 中设置懒加载功能。

<configuration>
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/> <!-- 控制是否在访问懒加载对象时立即加载 -->
    </settings>
</configuration>
  • lazmgIpmyLoadingEnabled: 设置为 true 时启用懒加载。
  • aggressiveLazyLoading: 如果设置为 true,当你访问一个对象的任何属性时,会立即加载所有懒加载的属性。如果设置为 false,那么只有在访问特定的懒加载属性时才会加载。

关于Mybatis和JDBC的使用及区别

5.3、实现

假设我们有两个实体:UserOrder,每个用户可以有多个订单。这是一个常见的一对多关系的例子。

1. 数据库表结构

CREATE TABLE users (
    user_id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL
);

CREATE TABLE orders (
    order_id INT PRIMARY KEY AUTO_INCREMENT,
    order_number VARCHAR(50) NOT NULL,
    user_id INT,
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

2. POJO 类

User.java

import java.util.List;

public class User {
    private Integer userId;
    private String username;
    private List<Order> orders; // 一对多关联

    // Getters 和 Setters
    // ...
}

Order.java

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

    // Getters 和 Setters
    // ...
}

3. Mapper 接口

我们创建一个 Mapper 接口来定义查询方法。

package com.example.mapper;

import com.example.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface UserMapper {
    @Select("SELECT * FROM users WHERE user_id = #{userId}")
    User findUserById(Integer userId);
}

4. XML 映射文件

为了懒加载订单数据,我们可以在 User 类中定义一个方法来获取订单。如果你不希望在获取用户时立即加载用户的所有订单数据,可以通过以下方式进行设计。

UserMapper.xml

<mapper namespace="com.example.mapper.UserMapper">

    <resultMap id="userResultMap" type="User">
        <id property="userId" column="user_id"/>
        <result property="username" column="username"/>
        <collection property="orders" ofType="Order" 
                     select="com.example.mapper.OrderMapper.findOrdersByUserId"
                     fetchType="lazy"/> <!-- 使用懒加载 -->
    </resultMap>

    <select id="findUserById" resultMap="userResultMap">
        SELECT * FROM users WHERE user_id = #{userId}
    </select>

</mapper>
  • UserMapper.xml 中, <collection> 标签表示与 Order 的一对多关系的懒加载配置。通过 select 属性指定查询订单的 SQL。
  • fetchType="lazy" 表示此集合数据将延迟加载。

OrderMapper.java

package com.example.mapper;

import com.example.model.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface OrderMapper {
    @Select("SELECT * FROM orders WHERE user_id = #{userId}")
    List<Order> findOrdersByUserId(Integer userId);
}

5. 使用懒加载的情况

在你的服务层中,你可以调用 findUserById 方法。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public User getUserWithOrders(Integer userId) {
        User user = userMapper.findUserById(userId);
        // Orders 还没有被加载,直到你调用 user.getOrders()
        return user;
    }
}

6. 控制器层

你可以使用 Spring MVC 来创建一个控制器,处理 HTTP 请求。

UserController.java:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User getUser(@PathVariable Integer id) {
        return userService.getUserWithOrders(id);
    }
}

7. 使用示例

当你访问 http://localhost:8080/users/1,会调用 getUser() 方法。

  1. 一开始,findUserById 方法会查询用户信息,但订单还不会被加载。
  2. 当你访问 user.getOrders() 时,MyBatis 会自动调用 OrderMapper.findOrdersByUserId 方法来查询与该用户相关的订单。

8. 总结

以上代码演示了如何在 MyBatis 中实现懒加载功能。通过配置 XML 文件中的 <collection> 标签结合 fetchType="lazy" 设置,可以确保在访问相关集合属性时才加载数据。这能帮助提高性能,减少不必要的数据加载。

懒加载机制在处理大数据量、频繁不使用的关联对象时尤为重要,有效节约系统资源。

6、常用问题

6.1、映射不一致

当实体类中的属性名和表中的字段名不一样 ,怎么办 ?

使用 @Results 注解或者 <resultMap> 来明确映射关系。

假设我们有一个数据库表 users,其结构如下:

关于Mybatis和JDBC的使用及区别

在项目中,有一个对应的 POJO 类 User,其属性使用驼峰命名风格:

public class User {
    private Integer id;        // 对应 user_id
    private String username;   // 对应 user_name
    private String email;      // 对应 user_email

   get set方法略
}

1、使用 XML(resultMap)

1. 创建 MyBatis 映射文件

将使用 XML 文件 UserMapper.xml 来定义 SQL 操作和字段映射。这个文件通常位于 resources 目录下。

UserMapper.xml:

<mapper namespace="com.example.mapper.UserMapper">

    <!-- 定义结果映射 -->
    <resultMap id="UserResultMap" type="User">
        <result property="id" column="user_id"/>
        <result property="username" column="user_name"/>
        <result property="email" column="user_email"/>
    </resultMap>

    <!-- 查询用户的 SQL 操作 -->
    <select id="getUserById" resultMap="UserResultMap">
        SELECT user_id, user_name, user_email FROM users WHERE user_id = #{id}
    </select>

</mapper>

2、使用(Results)

如果你想使用注解的方式,可以直接在 Mapper 接口中使用 @Select@Results 注解来实现。下面是一个示例:

UserMapper.java(注解方式):

package com.example.mapper;

import com.example.model.User;
import org.apache.ibatis.annotations.*;

@Mapper
public interface UserMapper {

    @Select("SELECT user_id, user_name, user_email FROM users WHERE user_id = #{id}")
    @Results({
        @Result(property = "id", column = "user_id"),
        @Result(property = "username", column = "user_name"),
        @Result(property = "email", column = "user_email")
    })
    User getUserById(Integer id);
}

3、JsonProperty

 
    @JsonProperty("user_id")    // 将 JSON 中的 user_id 映射到 id
    private Integer id;
    
    @JsonProperty("user_name")  // 将 JSON 中的 user_name 映射到 username
    private String username;
    
    @JsonProperty("user_email") // 将 JSON 中的 user_email 映射到 email
    private String email;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {
    public static void main(String[] args) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();
        
        // 创建一个 User 对象
        User user = new User();
        user.setId(1);
        user.setUsername("alice");
        user.setEmail("alice@example.com");

        // 将 User 对象序列化为 JSON 字符串
        String jsonString = objectMapper.writeValueAsString(user);
        System.out.println("Serialized JSON: " + jsonString);
    }
}

输出:
{"user_id":1,"user_name":"alice","user_email":"alice@example.com"}
package com.example.mapper;

import com.example.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface UserMapper {
    List<User> findUsersByUsernameLike(@Param("username") String username);
}

@JsonProperty 注解帮助在序列化和反序列化过程中,指定 JSON 字段与 POJO 属性之间的映射关系。

6.2、模糊查询

1.CONCAT

创建一个 Mapper 接口,并添加一个模糊查询的方法。

package com.example.mapper;

import com.example.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface UserMapper {
    List<User> findUsersByUsernameLike(@Param("username") String username);
}

在 Mapper XML 文件中定义模糊查询的 SQL 语句。我们将使用 LIKE 关键字进行模糊匹配。

UserMapper.xml:

<mapper namespace="com.example.mapper.UserMapper">

    <select id="findUsersByUsernameLike" resultType="User">
        SELECT user_id, username, email
        FROM users
        WHERE username LIKE CONCAT('%', #{username}, '%')
    </select>

</mapper>

2.传递参数%value%

在传入参数的时候比如username:%value% 传入到mapper文件里面;

6.3、动态标签

1. <if>

用于判断条件,如果条件满足则会生成 SQL 语句中的代码。

示例:

<select id="findUserByCriteria" resultType="User">
    SELECT * FROM users
    WHERE 1=1
    <if test="username != null">
        AND username = #{username}
    </if>
    <if test="email != null">
        AND email = #{email}
    </if>
</select>

在这个例子中,只有当 usernameemail 不为 null 时,相应的条件才会被添加到 SQL 中。

2. <choose><when><otherwise>

用于多条件选择的情况,类似于 Java 中的 switch-case 语句。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;

    public List<User> findUsers(String status) {
        return userMapper.findUsersByStatus(status);
    }
}
<select id="findUserByStatus" resultType="User">
    SELECT * FROM users
    WHERE 
    <choose>
        <when test="status == 'active'">
            status = 'active'
        </when>
        <when test="status == 'inactive'">
            status = 'inactive'
        </when>
        <otherwise>
            status IS NOT NULL
        </otherwise>
    </choose>
</select>

在这个例子中,SQL 查询将会根据 status 的值选择生成查询条件。

3.<foreach>

用于循环遍历集合,通常用于生成 IN 列表或多条 SQL 语句。

示例:

<select id="findUsersByIds" resultType="User">
    SELECT * FROM users
    WHERE user_id IN
    <foreach item="id" collection="userIds" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

4. <trim>

用于去掉 SQL 语句前后可能出现的多余的字符,主要是在构建可选条件时使用,查询参数可以是可选的。

<select id="findUsersByCondition" resultType="User">
    SELECT android* FROM users
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
        <if test="username != null">
            AND username = #{username}
        </if>
        <if test="email != null">
            AND email = #{email}
        </if>
    </trim>
</select>

<trim> 可以去掉多余的前缀,比如如果没有设定 usernameemail,可以避免生成 WHERE 后面紧跟着的 AND

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持China编程(www.chinasem.cn)。

这篇关于关于Mybatis和JDBC的使用及区别的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1154615

相关文章

java变量内存中存储的使用方式

《java变量内存中存储的使用方式》:本文主要介绍java变量内存中存储的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍2、变量的定义3、 变量的类型4、 变量的作用域5、 内存中的存储方式总结1、介绍在 Java 中,变量是用于存储程序中数据

macOS Sequoia 15.5 发布: 改进邮件和屏幕使用时间功能

《macOSSequoia15.5发布:改进邮件和屏幕使用时间功能》经过常规Beta测试后,新的macOSSequoia15.5现已公开发布,但重要的新功能将被保留到WWDC和... MACOS Sequoia 15.5 正式发布!本次更新为 Mac 用户带来了一系列功能强化、错误修复和安全性提升,进一步增

Java资源管理和引用体系的使用详解

《Java资源管理和引用体系的使用详解》:本文主要介绍Java资源管理和引用体系的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Java的引用体系1、强引用 (Strong Reference)2、软引用 (Soft Reference)3、弱引用 (W

ubuntu系统使用官方操作命令升级Dify指南

《ubuntu系统使用官方操作命令升级Dify指南》Dify支持自动化执行、日志记录和结果管理,适用于数据处理、模型训练和部署等场景,今天我们就来看看ubuntu系统中使用官方操作命令升级Dify的方... Dify 是一个基于 docker 的工作流管理工具,旨在简化机器学习和数据科学领域的多步骤工作流。

C++类和对象之初始化列表的使用方式

《C++类和对象之初始化列表的使用方式》:本文主要介绍C++类和对象之初始化列表的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C++初始化列表详解:性能优化与正确实践什么是初始化列表?初始化列表的三大核心作用1. 性能优化:避免不必要的赋值操作2. 强

IDEA之MyBatisX使用的图文步骤

《IDEA之MyBatisX使用的图文步骤》本文主要介绍了IDEA之MyBatisX使用,文中通过图文示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习... 目录一、idea插件安装二、IDEA配置数据库连接(以mysql为例)三、生产基础代码一、idea插

Python的pip在命令行无法使用问题的解决方法

《Python的pip在命令行无法使用问题的解决方法》PIP是通用的Python包管理工具,提供了对Python包的查找、下载、安装、卸载、更新等功能,安装诸如Pygame、Pymysql等Pyt... 目录前言一. pip是什么?二. 为什么无法使用?1. 当我们在命令行输入指令并回车时,一般主要是出现以

Java使用WebView实现桌面程序的技术指南

《Java使用WebView实现桌面程序的技术指南》在现代软件开发中,许多应用需要在桌面程序中嵌入Web页面,例如,你可能需要在Java桌面应用中嵌入一部分Web前端,或者加载一个HTML5界面以增强... 目录1、简述2、WebView 特点3、搭建 WebView 示例3.1 添加 JavaFX 依赖3

Java Jackson核心注解使用详解

《JavaJackson核心注解使用详解》:本文主要介绍JavaJackson核心注解的使用,​​Jackson核心注解​​用于控制Java对象与JSON之间的序列化、反序列化行为,简化字段映射... 目录前言一、@jsonProperty-指定JSON字段名二、@JsonIgnore-忽略字段三、@Jso

MySQL中隔离级别的使用详解

《MySQL中隔离级别的使用详解》:本文主要介绍MySQL中隔离级别的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录引言undo log的作用MVCC的实现有以下几个重要因素如何根据这些因素判断数据值?可重复读和已提交读区别?串行化隔离级别的实现幻读和可