开发者

关于Mybatis和JDBC的使用及区别

目录
  • 1、JDBC
    • 1.1、流程
    • 1.2、优缺点
  • 2、MyBATis
    • 2.1、执行流程
    • 2.2、使用
    • 2.3、实现方式
      • 1、XML配置文件
      • 2、注解
      • 3、动态 SQL
      • 4、MyBatis-Plus 扩展实现
  • 3、缓存
    • 3.1. 一级缓存
      • 3.2. 二级缓存
        • 3.3. 缓存区别
        • 4、Sql注入
          • 4.1.#{}参数占位符
            • 4.2.${}字符串拼接
              • 4.3.SQL注入
                • 4.4.用法总结
                • 5、懒加载
                  • 6、常用问题
                    • 6.1、映射不一致
                      • 1、使用 XML(resultMap)
                      • 2、使用(Results)
                      • 3、jsonProperty
                    • 6.2、模糊查询
                      • 6.3、动态标签
                      • 总结

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

                        1、JDBC

                        1.1、流程

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

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

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

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

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

                        import java.sql.*;
                        
                        public class JdbcExample {
                        
                            public static void main(String[] args) {
                                Connection connection = null;
                                PreparedStatement preparedStatement = null;
                                ResultSet resultSet = null;
                        
                                try {
                                    // 1. 加载数据库驱动
                                    Class.forName("com.mysql.cj.jdbc.Driver"); // 替换为相应驱动
                        
                                    // 2. 建立数据库连接
                                    String url = "jdbc:mysql://localhost:3306/yourdatabase"; // 数据库 URL
                                    String user = "yourusername"; // 数据库用户名
                                    String password = "yourpassword"; // 数据库密码
                                    connection = DriverManager.getConnection(url, user, password);
                        
                                    // 3. 创建 PreparedStatement
                                    String sql = "SELECT * FROM users WHERE id = ?";
                                    preparedStatement = connection.prepareStatement(sql);
                                    preparedStatement.setLong(1, 1); // 设置参数
                        
                                    // 4. 执行查询
                                    resultSet = preparedStatement.executeQuery();
                        
                                    // 5. 处理结果
                                    while (resultSet.next()) {
                                        System.out.println("User ID: " + resultSet.getInt("id"));
                                        System.out.println("User Name: " + resultSet.getString("name"));
                                        System.out.println("User Email: " + resultSet.getString("email"));
                                    }
                                } catch (ClassNotFoundException e) {
                                    e.printStackTrace();
                                } catch (SQLException e) {
                                    e.printStackTrace();
                                } finally {
                                    // 6. 关闭资源
                                    try {
                                        if (resultSet != null) resultSet.close();
                                        if (preparedStatement != null) preparedStatement.close();
                                        if (connection != null) connection.close();
                                    } catch (SQLException e) {
                                        e.printStackTrace();
                                    }
                                }
                            }
                        }

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

                        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]编程客栈 - ==> 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

                        总结

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

                        0

                        上一篇:

                        下一篇:

                        精彩评论

                        暂无评论...
                        验证码 换一张
                        取 消

                        最新开发

                        开发排行榜