qidao123.com技术社区-IT企服评测·应用市场

标题: JDBC、MyBatis 、MyBatis-Plus面试总结(一) [打印本页]

作者: 吴旭华    时间: 2025-3-24 20:22
标题: JDBC、MyBatis 、MyBatis-Plus面试总结(一)
以下为你整理了一些 MyBatis 和 MyBatis-Plus 中 mapper.xml 相干的常晤面试问题及答案:
基础概念类

问题 1:什么是 mapper.xml 文件,它在 MyBatis 中有什么作用?

答案:mapper.xml 文件是 MyBatis 中用于定义 SQL 语句的配置文件。它将 SQL 语句与 Java 代码分离,使得 SQL 语句的管理和维护更加方便。在 mapper.xml 中可以定义各种 SQL 利用,如查询、插入、更新和删除等,通过映射关系将 SQL 执行结果映射到 Java 对象上。
问题 2:MyBatis-Plus 中为什么还须要 mapper.xml 文件?

答案:固然 MyBatis-Plus 提供了很多内置的 CRUD 方法,可以减少编写 SQL 语句的工作量,但在以下情况下仍然须要利用 mapper.xml 文件:

配置与利用类

问题 3:如何在 mapper.xml 中定义一个简单的查询语句?

答案:以下是一个简单的查询示例,假设我们有一个 User 表,对应的 Java 实体类为 User:
  1. <mapper namespace="com.example.mapper.UserMapper">
  2.     <select id="selectUserById" resultType="com.example.entity.User">
  3.         SELECT * FROM user WHERE id = #{id}
  4.     </select>
  5. </mapper>
复制代码
在上述代码中,namespace 指定了该 mapper.xml 文件对应的 Mapper 接口的全限定名,select 标签用于定义查询语句,id 是该查询方法的唯一标识,resultType 指定了查询结果的映射类型。
问题 4:mapper.xml 中的 #{} 和 ${} 有什么区别?

答案

问题 5:如何在 mapper.xml 中实现动态 SQL?

答案:MyBatis 提供了多种标签来实现动态 SQL,常见的有 <if>、<choose>、<when>、<otherwise>、<where>、<set>、<foreach> 等。
以下是一个利用 <if> 和 <where> 标签实现动态查询的示例:
  1. <select id="selectUserByCondition" resultType="com.example.entity.User">
  2.     SELECT * FROM user
  3.     <where>
  4.         <if test="username != null and username != ''">
  5.             AND username = #{username}
  6.         </if>
  7.         <if test="age != null">
  8.             AND age = #{age}
  9.         </if>
  10.     </where>
  11. </select>
复制代码
在上述代码中,<where> 标签会自动处理 SQL 语句中的 AND 和 OR 关键字,避免出现多余的 AND 或 OR。<if> 标签用于根据条件判断是否拼接相应的 SQL 片断。
高级特性类

问题 6:mapper.xml 中如何实现关联查询和结果映射?

答案:可以利用 <resultMap> 标签来实现关联查询和结果映射。以下是一个简单的示例,假设我们有 User 和 Order 两个表,一个用户可以有多个订单:
  1. <mapper namespace="com.example.mapper.UserMapper">
  2.     <resultMap id="UserResultMap" type="com.example.entity.User">
  3.         <id property="id" column="user_id"/>
  4.         <result property="username" column="username"/>
  5.         <collection property="orders" ofType="com.example.entity.Order">
  6.             <id property="id" column="order_id"/>
  7.             <result property="orderNo" column="order_no"/>
  8.         </collection>
  9.     </resultMap>
  10.     <select id="selectUserWithOrders" resultMap="UserResultMap">
  11.         SELECT u.id AS user_id, u.username, o.id AS order_id, o.order_no
  12.         FROM user u
  13.         LEFT JOIN orders o ON u.id = o.user_id
  14.     </select>
  15. </mapper>
复制代码
在上述代码中,<resultMap> 标签定义了查询结果的映射规则,<id> 标签用于映射主键,<result> 标签用于映射平凡字段,<collection> 标签用于处理一对多的关联关系。
问题 7:如何在 mapper.xml 中利用存储过程?

答案:可以利用 <select> 或 <call> 标签来调用存储过程。以下是一个调用存储过程的示例:
  1. <mapper namespace="com.example.mapper.UserMapper">
  2.     <select id="callProcedure" resultType="com.example.entity.User">
  3.         {call get_user_info(#{id, mode=IN, jdbcType=INTEGER})}
  4.     </select>
  5. </mapper>
复制代码
在上述代码中,{call get_user_info(...)} 表示调用名为 get_user_info 的存储过程,#{id, mode=IN, jdbcType=INTEGER} 表示传入一个输入参数 id。
性能与优化类

问题 8:如何优化 mapper.xml 中的 SQL 语句以提高性能?

答案:可以从以下几个方面进行优化:

问题 9:在 mapper.xml 中,如何处理大结果集以避免内存溢出?

答案:可以利用流式查询来处理大结果集。在 MyBatis 中,可以通过设置 fetchSize 属性来实现流式查询。例如:
  1. <select id="selectLargeResult" resultType="com.example.entity.User" fetchSize="100">
  2.     SELECT * FROM user
  3. </select>
复制代码
在上述代码中,fetchSize="100" 表示每次从数据库中获取 100 条记载,避免一次性将全部记载加载到内存中。
缓存相干问题

问题 10:MyBatis 中 mapper.xml 如何配置一级缓存和二级缓存?它们有什么区别?

答案

  1. <mapper namespace="com.example.mapper.UserMapper">
  2.     <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
  3.     <!-- 其他 SQL 语句 -->
  4. </mapper>
复制代码
其中,eviction 表示缓存的回收策略(如 LRU 近来最少利用),flushInterval 表示缓存革新间隔,size 表示缓存对象的最大数目,readOnly 表示缓存是否只读。
区别

问题 11:如何在 mapper.xml 中手动控制缓存革新?

答案:可以在 mapper.xml 中利用 <flushCache> 属性来手动控制缓存革新。在 <select>、<insert>、<update>、<delete> 标签中都可以利用该属性。例如:
  1. <update id="updateUser" flushCache="true">
  2.     UPDATE user SET username = #{username} WHERE id = #{id}
  3. </update>
复制代码
当 flushCache="true" 时,执行该 SQL 语句后会清空一级缓存和二级缓存。
映射与类型转换问题

问题 12:在 mapper.xml 中,如何处理数据库字段类型与 Java 对象属性类型不匹配的情况?

答案

  1. <resultMap id="UserResultMap" type="com.example.entity.User">
  2.     <result property="createTime" column="create_time" typeHandler="com.example.handler.DateTypeHandler"/>
  3. </resultMap>
复制代码

  1. <select id="selectUser" resultType="com.example.entity.User">
  2.     SELECT CAST(age AS SIGNED) AS age FROM user
  3. </select>
复制代码
问题 13:mapper.xml 中 <resultMap> 的 <association> 和 <collection> 标签在处理关联关系时有什么高级用法?

答案

  1. <resultMap id="UserResultMap" type="com.example.entity.User">
  2.     <id property="id" column="user_id"/>
  3.     <association property="department" javaType="com.example.entity.Department" fetchType="lazy">
  4.         <id property="id" column="dept_id"/>
  5.         <result property="deptName" column="dept_name"/>
  6.     </association>
  7. </resultMap>
复制代码

  1. <resultMap id="UserResultMap" type="com.example.entity.User">
  2.     <id property="id" column="user_id"/>
  3.     <collection property="orders" ofType="com.example.entity.Order" fetchType="lazy" columnPrefix="order_">
  4.         <id property="id" column="id"/>
  5.         <result property="orderNo" column="order_no"/>
  6.     </collection>
  7. </resultMap>
复制代码
性能监控与调优问题

问题 14:如何在 mapper.xml 层面监控 SQL 执行性能?

答案

  1. log4j.logger.com.example.mapper=DEBUG
复制代码

问题 15:在 mapper.xml 中,如何优化批量插入利用的性能?

答案

  1. <insert id="batchInsertUsers">
  2.     INSERT INTO user (username, age) VALUES
  3.     <foreach collection="users" item="user" separator=",">
  4.         (#{user.username}, #{user.age})
  5.     </foreach>
  6. </insert>
复制代码

  1. SqlSession sqlSession = sqlSessionFactory.openSession(false);
  2. try {
  3.     UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  4.     userMapper.batchInsertUsers(users);
  5.     sqlSession.commit();
  6. } catch (Exception e) {
  7.     sqlSession.rollback();
  8. } finally {
  9.     sqlSession.close();
  10. }
复制代码
以下是一些关于MyBatis和MyBatis Plus中mapper.xml的其他面试问题及答案:
动态SQL相干问题

问题16:MyBatis的mapper.xml中<if>、<choose>、<when>、<otherwise>标签在动态SQL中有什么作用?如何利用?

答案

  1. <select id="selectUsers" resultMap="UserResultMap">
  2.     SELECT * FROM user
  3.     <where>
  4.         <if test="username!= null">
  5.             AND username = #{username}
  6.         </if>
  7.         <if test="age!= null">
  8.             AND age = #{age}
  9.         </if>
  10.     </where>
  11. </select>
复制代码
上述代码根据username和age是否为null来动态添加查询条件。

  1. <select id="selectUsersByCondition" resultMap="UserResultMap">
  2.     SELECT * FROM user
  3.     <where>
  4.         <choose>
  5.             <when test="condition == 1">
  6.                 AND age &gt; 18
  7.             </when>
  8.             <when test="condition == 2">
  9.                 AND age &lt;= 18
  10.             </when>
  11.             <otherwise>
  12.                 AND gender = '男'
  13.             </otherwise>
  14.         </choose>
  15.     </where>
  16. </select>
复制代码
根据condition的值来选择不同的查询条件,如果condition既不等于1也不等于2,则利用otherwise中的条件。
问题17:<foreach>标签有哪些属性?在批量利用中如何利用?

答案

  1. <delete id="batchDeleteUsers">
  2.     DELETE FROM user WHERE id IN
  3.     <foreach collection="ids" item="id" open="(" close=")" separator=",">
  4.         #{id}
  5.     </foreach>
  6. </delete>
复制代码
在上述代码中,ids是一个包罗用户ID的集合,通过<foreach>标签将集合中的ID拼接成IN子句中的参数列表,实现批量删除利用。
多表关联查扣问题

问题18:在mapper.xml中如何进行复杂的多表联合查询?

答案:可以利用JOIN关键字进行多表联合查询,并通过resultMap来映射结果。例如,查询用户及其所属部分,以及该部分下的全部员工:
  1. <resultMap id="UserDeptEmpResultMap" type="com.example.entity.User">
  2.     <id property="id" column="user_id"/>
  3.     <result property="username" column="username"/>
  4.     <association property="department" javaType="com.example.entity.Department">
  5.         <id property="id" column="dept_id"/>
  6.         <result property="deptName" column="dept_name"/>
  7.         <collection property="employees" ofType="com.example.entity.User">
  8.             <id property="id" column="emp_id"/>
  9.             <result property="username" column="emp_username"/>
  10.         </collection>
  11.     </association>
  12. </resultMap>
  13. <select id="selectUserDeptEmp" resultMap="UserDeptEmpResultMap">
  14.     SELECT
  15.         u.id AS user_id,
  16.         u.username,
  17.         d.id AS dept_id,
  18.         d.dept_name,
  19.         e.id AS emp_id,
  20.         e.username AS emp_username
  21.     FROM
  22.         user u
  23.     JOIN
  24.         department d ON u.dept_id = d.id
  25.     LEFT JOIN
  26.         user e ON d.id = e.dept_id
  27. </select>
复制代码
上述代码中,通过JOIN和LEFT JOIN实现了多表关联查询,并利用resultMap对复杂的结果进行了映射。
问题19:在多表关联查询时,如何处理关联字段的别名辩说问题?

答案:可以在SQL查询中为关联字段指定不同的别名来避免辩说。例如:
  1. <select id="selectUserAndOrders" resultMap="UserOrdersResultMap">
  2.     SELECT
  3.         u.id AS user_id,
  4.         u.username,
  5.         o.id AS order_id,
  6.         o.order_no,
  7.         o.order_amount
  8.     FROM
  9.         user u
  10.     JOIN
  11.         orders o ON u.id = o.user_id
  12. </select>
复制代码
在resultMap中利用对应的别名进行映射:
  1. <resultMap id="UserOrdersResultMap" type="com.example.entity.User">
  2.     <id property="id" column="user_id"/>
  3.     <result property="username" column="username"/>
  4.     <collection property="orders" ofType="com.example.entity.Order">
  5.         <id property="id" column="order_id"/>
  6.         <result property="orderNo" column="order_no"/>
  7.         <result property="orderAmount" column="order_amount"/>
  8.     </collection>
  9. </resultMap>
复制代码
MyBatis Plus特有的mapper.xml问题

问题20:MyBatis Plus中mapper.xml与MyBatis的mapper.xml有什么区别和联系?

答案

问题21:在MyBatis Plus的mapper.xml中如何实现自定义分页查询?

答案:起首在mapper.xml中编写查询语句,利用${ew.customSqlSegment}来吸收MyBatis Plus自动天生的分页相干的SQL片断。例如:
  1. <select id="selectUsersByConditionWithPage" resultMap="UserResultMap">
  2.     SELECT * FROM user
  3.     <where>
  4.         <!-- 自定义查询条件 -->
  5.         <if test="username!= null">
  6.             AND username = #{username}
  7.         </if>
  8.     </where>
  9.     ${ew.customSqlSegment}
  10. </select>
复制代码
在Java代码中,利用Page对象和QueryWrapper来进行分页查询:
  1. Page<User> page = new Page<>(1, 10);
  2. QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  3. queryWrapper.eq("gender", "女");
  4. userMapper.selectUsersByConditionWithPage(page, queryWrapper);
复制代码
事务与并发控制问题

问题 22:在 mapper.xml 相干利用中,如何保证事务的一致性?

答案

  1. @Service
  2. public class UserService {
  3.     @Autowired
  4.     private UserMapper userMapper;
  5.     @Transactional
  6.     public void updateUserAndOrder(User user, Order order) {
  7.         userMapper.updateUser(user);
  8.         userMapper.insertOrder(order);
  9.     }
  10. }
复制代码

  1. SqlSession sqlSession = sqlSessionFactory.openSession(false);
  2. try {
  3.     UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  4.     userMapper.updateUser(user);
  5.     userMapper.insertOrder(order);
  6.     sqlSession.commit();
  7. } catch (Exception e) {
  8.     sqlSession.rollback();
  9. } finally {
  10.     sqlSession.close();
  11. }
复制代码
问题 23:在高并发场景下,mapper.xml 中的 SQL 利用可能会出现哪些问题?如何解决?

答案

代码维护与扩展性问题

问题 24:如何提高 mapper.xml 文件的可维护性和扩展性?

答案

  1. <sql id="userColumns">
  2.     id, username, age, gender
  3. </sql>
  4. <select id="selectUserById" resultType="com.example.entity.User">
  5.     SELECT <include refid="userColumns"/> FROM user WHERE id = #{id}
  6. </select>
复制代码

问题 25:当业务需求变更时,如何修改 mapper.xml 中的 SQL 语句以最小化对现有代码的影响?

答案

性能调优深入问题

问题 26:在 mapper.xml 中,如何利用数据库索引来优化查询性能?

答案

  1. <!-- 正确使用索引 -->
  2. <select id="selectUserByUsername" resultType="com.example.entity.User">
  3.     SELECT * FROM user WHERE username = #{username}
  4. </select>
  5. <!-- 错误示例,可能导致索引失效 -->
  6. <select id="selectUserByUsernameWrong" resultType="com.example.entity.User">
  7.     SELECT * FROM user WHERE UPPER(username) = UPPER(#{username})
  8. </select>
复制代码

问题 27:对于 mapper.xml 中的复杂 SQL 查询,如何进行性能分析和优化?

答案

与其他框架集成问题

问题 28:当 MyBatis 的 mapper.xml 与 Spring Boot 集成时,有哪些常见的配置和注意事项?

答案

问题 29:如果要将 mapper.xml 与 Redis 集成实现缓存,该如何利用?

答案

  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
复制代码

  1. spring.redis.host=localhost
  2. spring.redis.port=6379
复制代码

  1. @Service
  2. public class UserService {
  3.     @Autowired
  4.     private RedisTemplate<String, Object> redisTemplate;
  5.     @Autowired
  6.     private UserMapper userMapper;
  7.     public User getUserById(Long id) {
  8.         String key = "user:" + id;
  9.         User user = (User) redisTemplate.opsForValue().get(key);
  10.         if (user == null) {
  11.             user = userMapper.selectUserById(id);
  12.             if (user != null) {
  13.                 redisTemplate.opsForValue().set(key, user);
  14.             }
  15.         }
  16.         return user;
  17.     }
  18. }
复制代码
代码规范与最佳实践问题

问题 30:在编写 mapper.xml 时,有哪些代码规范和最佳实践?

答案

问题 31:如何对 mapper.xml 中的 SQL 语句进行安全审查?

答案

异常处理与调试问题

问题 32:在利用 mapper.xml 时,可能会遇到哪些异常?如何进行调试和解决?

答案

问题 33:当 mapper.xml 中的 SQL 执行超时,应该如何处理?

答案

  1. @Configuration
  2. public class DataSourceConfig {
  3.     @Bean
  4.     public DataSource dataSource() {
  5.         HikariDataSource dataSource = new HikariDataSource();
  6.         dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/your_database");
  7.         dataSource.setUsername("your_username");
  8.         dataSource.setPassword("your_password");
  9.         dataSource.setConnectionTimeout(30000); // 连接超时时间 30 秒
  10.         dataSource.setIdleTimeout(600000); // 空闲连接超时时间 10 分钟
  11.         dataSource.setMaxLifetime(1800000); // 最大连接生命周期 30 分钟
  12.         dataSource.setMaximumPoolSize(10); // 最大连接池大小
  13.         return dataSource;
  14.     }
  15. }
复制代码
分布式情况相干问题

问题 34:在分布式体系中,mapper.xml 里的 SQL 利用如何保证数据一致性?

答案

  1. @Service
  2. public class DistributedService {
  3.     @Autowired
  4.     private UserMapper userMapper;
  5.     @Autowired
  6.     private OrderMapper orderMapper;
  7.     @GlobalTransactional
  8.     public void createUserAndOrder(User user, Order order) {
  9.         userMapper.insertUser(user);
  10.         orderMapper.insertOrder(order);
  11.     }
  12. }
复制代码

问题 35:在分布式缓存场景下,mapper.xml 相干的 SQL 查询如何与缓存配合以提升性能?

答案

  1. public User getUserById(Long id) {
  2.     String key = "user:" + id;
  3.     User user = (User) redisTemplate.opsForValue().get(key);
  4.     if (user == null) {
  5.         if (bloomFilter.mightContain(id)) {
  6.             user = userMapper.selectUserById(id);
  7.             if (user != null) {
  8.                 redisTemplate.opsForValue().set(key, user);
  9.             } else {
  10.                 redisTemplate.opsForValue().set(key, null, 60, TimeUnit.SECONDS); // 设置空值缓存,避免缓存穿透
  11.             }
  12.         }
  13.     }
  14.     return user;
  15. }
复制代码

  1. @Transactional
  2. public void updateUser(User user) {
  3.     userMapper.updateUser(user);
  4.     String key = "user:" + user.getId();
  5.     redisTemplate.delete(key);
  6. }
复制代码

动态数据源问题

问题 36:在利用动态数据源的情况下,mapper.xml 如何适配不同的数据源?

答案

  1. @Configuration
  2. public class DynamicDataSourceConfig {
  3.     @Bean
  4.     public DataSource dynamicDataSource() {
  5.         DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
  6.         Map<Object, Object> targetDataSources = new HashMap<>();
  7.         targetDataSources.put("dataSource1", dataSource1());
  8.         targetDataSources.put("dataSource2", dataSource2());
  9.         dynamicRoutingDataSource.setDefaultTargetDataSource(dataSource1());
  10.         dynamicRoutingDataSource.setTargetDataSources(targetDataSources);
  11.         return dynamicRoutingDataSource;
  12.     }
  13.     @Bean
  14.     public DataSource dataSource1() {
  15.         // 配置数据源 1
  16.     }
  17.     @Bean
  18.     public DataSource dataSource2() {
  19.         // 配置数据源 2
  20.     }
  21. }
复制代码

  1. <select id="selectUser" resultType="com.example.entity.User">
  2.     <choose>
  3.         <when test="@com.example.util.DataSourceContextHolder@getDataSource() == 'dataSource1'">
  4.             -- 数据源 1 的特定 SQL 语句
  5.         </when>
  6.         <when test="@com.example.util.DataSourceContextHolder@getDataSource() == 'dataSource2'">
  7.             -- 数据源 2 的特定 SQL 语句
  8.         </when>
  9.         <otherwise>
  10.             -- 默认 SQL 语句
  11.         </otherwise>
  12.     </choose>
  13. </select>
复制代码
问题 37:如何在 mapper.xml 中实现对不同数据库方言的支持?

答案

  1. <select id="selectUserCount" resultType="int">
  2.     <choose>
  3.         <when test="@com.example.util.DbDialectUtil@isMysql()">
  4.             SELECT COUNT(*) FROM user
  5.         </when>
  6.         <when test="@com.example.util.DbDialectUtil@isOracle()">
  7.             SELECT COUNT(*) FROM user_table
  8.         </when>
  9.         <otherwise>
  10.             -- 默认 SQL 语句
  11.         </otherwise>
  12.     </choose>
  13. </select>
复制代码

  1. <sql id="userColumns">
  2.     id, username, age
  3. </sql>
  4. <select id="selectUser" resultType="com.example.entity.User">
  5.     <choose>
  6.         <when test="@com.example.util.DbDialectUtil@isMysql()">
  7.             SELECT <include refid="userColumns"/> FROM user
  8.         </when>
  9.         <when test="@com.example.util.DbDialectUtil@isOracle()">
  10.             SELECT <include refid="userColumns"/> FROM user_table
  11.         </when>
  12.         <otherwise>
  13.             -- 默认 SQL 语句
  14.         </otherwise>
  15.     </choose>
  16. </select>
复制代码
代码天生与自动化问题

问题 38:如何利用代码天生工具自动天生 mapper.xml 文件?

答案

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE generatorConfiguration
  3.         PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  4.         "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
  5. <generatorConfiguration>
  6.     <context id="DB2Tables" targetRuntime="MyBatis3">
  7.         <commentGenerator>
  8.             <property name="suppressDate" value="true"/>
  9.             <property name="suppressAllComments" value="true"/>
  10.         </commentGenerator>
  11.         <jdbcConnection driverClass="com.mysql.jdbc.Driver"
  12.                         connectionURL="jdbc:mysql://localhost:3306/your_database"
  13.                         userId="your_username"
  14.                         password="your_password">
  15.         </jdbcConnection>
  16.         <javaTypeResolver>
  17.             <property name="forceBigDecimals" value="false"/>
  18.         </javaTypeResolver>
  19.         <javaModelGenerator targetPackage="com.example.entity"
  20.                             targetProject="src/main/java">
  21.             <property name="enableSubPackages" value="true"/>
  22.             <property name="trimStrings" value="true"/>
  23.         </javaModelGenerator>
  24.         <sqlMapGenerator targetPackage="com.example.mapper"
  25.                          targetProject="src/main/resources">
  26.             <property name="enableSubPackages" value="true"/>
  27.         </sqlMapGenerator>
  28.         <javaClientGenerator type="XMLMAPPER"
  29.                              targetPackage="com.example.mapper"
  30.                              targetProject="src/main/java">
  31.             <property name="enableSubPackages" value="true"/>
  32.         </javaClientGenerator>
  33.         <table tableName="user"/>
  34.     </context>
  35. </generatorConfiguration>
复制代码
  1. - **运行生成器**:在 Java 代码中运行生成器,执行代码生成操作。
复制代码
  1. public class Generator {
  2.     public static void main(String[] args) throws Exception {
  3.         List<String> warnings = new ArrayList<>();
  4.         boolean overwrite = true;
  5.         File configFile = new File("generatorConfig.xml");
  6.         ConfigurationParser cp = new ConfigurationParser(warnings);
  7.         Configuration config = cp.parseConfiguration(configFile);
  8.         DefaultShellCallback callback = new DefaultShellCallback(overwrite);
  9.         MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
  10.         myBatisGenerator.generate(null);
  11.     }
  12. }
复制代码

  1. <dependency>
  2.     <groupId>com.baomidou</groupId>
  3.     <artifactId>mybatis-plus-generator</artifactId>
  4.     <version>x.x.x</version>
  5. </dependency>
复制代码
  1. - **编写生成代码**:通过 Java 代码配置生成器并执行生成操作。
复制代码
  1. public class CodeGenerator {
  2.     public static void main(String[] args) {
  3.         FastAutoGenerator.create("jdbc:mysql://localhost:3306/your_database", "your_username", "your_password")
  4.                .globalConfig(builder -> {
  5.                     builder.author("your_name") // 设置作者
  6.                            .outputDir("src/main/java"); // 指定输出目录
  7.                 })
  8.                .packageConfig(builder -> {
  9.                     builder.parent("com.example") // 设置父包名
  10.                            .moduleName("module") // 设置模块名
  11.                            .pathInfo(Collections.singletonMap(OutputFile.xml, "src/main/resources/mapper")); // 设置mapperXml生成路径
  12.                 })
  13.                .strategyConfig(builder -> {
  14.                     builder.addInclude("user") // 设置需要生成的表名
  15.                            .addTablePrefix("t_", "c_"); // 设置过滤表前缀
  16.                 })
  17.                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
  18.                .execute();
  19.     }
  20. }
复制代码
问题 39:自动天生的 mapper.xml 文件如何进行定制化修改和扩展?

答案

  1. <mapper namespace="com.example.mapper.UserMapper">
  2.     <!-- 自动生成的 SQL 语句 -->
  3.     <select id="selectUserById" resultType="com.example.entity.User">
  4.         SELECT * FROM user WHERE id = #{id}
  5.     </select>
  6.     <!-- 自定义 SQL 语句 -->
  7.     <select id="selectUserByUsernameAndAge" resultType="com.example.entity.User">
  8.         SELECT * FROM user WHERE username = #{username} AND age = #{age}
  9.     </select>
  10. </mapper>
复制代码

  1. <resultMap id="UserResultMap" type="com.example.entity.User">
  2.     <id property="id" column="id"/>
  3.     <result property="username" column="username"/>
  4.     <result property="age" column="age"/>
  5.     <!-- 扩展映射字段 -->
  6.     <result property="email" column="email"/>
  7. </resultMap>
复制代码
日志与审计相干问题

问题 40:如何在 mapper.xml 相干利用中记载详细的日志,以便进行审计和问题排查?

答案

  1. log4j.rootLogger=DEBUG, stdout
  2. log4j.appender.stdout=org.apache.log4j.ConsoleAppender
  3. log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
  4. log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
  5. log4j.logger.com.example.mapper=DEBUG
复制代码
  1. - **使用 Spring Boot 配置**:在 `application.properties` 中配置:
复制代码
  1. mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
复制代码

  1. @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
  2. public class SqlLogInterceptor implements Interceptor {
  3.     private static final Logger logger = LoggerFactory.getLogger(SqlLogInterceptor.class);
  4.     @Override
  5.     public Object intercept(Invocation invocation) throws Throwable {
  6.         long startTime = System.currentTimeMillis();
  7.         Object result = invocation.proceed();
  8.         long endTime = System.currentTimeMillis();
  9.         long executionTime = endTime - startTime;
  10.         MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
  11.         String sqlId = mappedStatement.getId();
  12.         logger.info("SQL ID: {}, Execution Time: {} ms, Result: {}", sqlId, executionTime, result);
  13.         return result;
  14.     }
  15.     @Override
  16.     public Object plugin(Object target) {
  17.         return Plugin.wrap(target, this);
  18.     }
  19.     @Override
  20.     public void setProperties(Properties properties) {
  21.         // 可用于设置拦截器的属性
  22.     }
  23. }
复制代码
然后在 MyBatis 配置中注册该拦截器:
  1. @Configuration
  2. public class MyBatisConfig {
  3.     @Autowired
  4.     private SqlSessionFactory sqlSessionFactory;
  5.     @PostConstruct
  6.     public void addInterceptor() {
  7.         sqlSessionFactory.getConfiguration().addInterceptor(new SqlLogInterceptor());
  8.     }
  9. }
复制代码
问题 41:对于 mapper.xml 中的敏感数据利用,如何进行安全审计?

答案

  1. <insert id="insertAuditLog">
  2.     INSERT INTO audit_log (operation_time, operator, sql_statement, affected_rows)
  3.     VALUES (#{operationTime}, #{operator}, #{sqlStatement}, #{affectedRows})
  4. </insert>
复制代码
在服务层调用该插入语句:
  1. @Service
  2. public class SensitiveDataService {
  3.     @Autowired
  4.     private AuditLogMapper auditLogMapper;
  5.     @Autowired
  6.     private SensitiveDataMapper sensitiveDataMapper;
  7.     public void updateSensitiveData(SensitiveData data) {
  8.         String sqlStatement = "UPDATE sensitive_data SET ...";
  9.         int affectedRows = sensitiveDataMapper.updateSensitiveData(data);
  10.         AuditLog auditLog = new AuditLog();
  11.         auditLog.setOperationTime(new Date());
  12.         auditLog.setOperator("user1");
  13.         auditLog.setSqlStatement(sqlStatement);
  14.         auditLog.setAffectedRows(affectedRows);
  15.         auditLogMapper.insertAuditLog(auditLog);
  16.     }
  17. }
复制代码
数据迁移与兼容性问题

问题 42:当数据库进行升级或迁移时,mapper.xml 中的 SQL 语句须要做哪些调解?

答案

  1. <!-- MySQL 查询分页 -->
  2. <select id="selectUsersByPage" resultType="com.example.entity.User">
  3.     SELECT * FROM user LIMIT #{offset}, #{limit}
  4. </select>
  5. <!-- PostgreSQL 查询分页 -->
  6. <select id="selectUsersByPage" resultType="com.example.entity.User">
  7.     SELECT * FROM user OFFSET #{offset} LIMIT #{limit}
  8. </select>
复制代码

  1. <resultMap id="UserResultMap" type="com.example.entity.User">
  2.     <!-- 修改前 -->
  3.     <!-- <result property="username" column="user_name"/> -->
  4.     <!-- 修改后 -->
  5.     <result property="username" column="username"/>
  6. </resultMap>
  7. <select id="selectUserById" resultType="com.example.entity.User">
  8.     <!-- 修改前 -->
  9.     <!-- SELECT user_name FROM user WHERE id = #{id} -->
  10.     <!-- 修改后 -->
  11.     SELECT username FROM user WHERE id = #{id}
  12. </select>
复制代码

问题 43:如何确保 mapper.xml 中的 SQL 语句在不同数据库版本之间的兼容性?

答案

  1. <select id="selectUserCount" resultType="int">
  2.     <choose>
  3.         <when test="@com.example.util.DbVersionUtil@isOldVersion()">
  4.             -- 旧版本数据库的 SQL 语句
  5.         </when>
  6.         <otherwise>
  7.             -- 新版本数据库的 SQL 语句
  8.         </otherwise>
  9.     </choose>
  10. </select>
复制代码

代码优化与重构问题

问题 44:如何对已有的 mapper.xml 文件进行性能优化和代码重构?

答案

问题 45:当 mapper.xml 文件变得非常巨大时,如何进行拆分和管理?

答案

  1. <mappers>
  2.     <package name="com.example.mapper"/>
  3. </mappers>
复制代码

MyBatis、MyBatis-Plus和JDBC之间存在一定的关系,它们的底层原理也各有特点,具体如下:
三者关系


底层原理


如何在MyBatis中利用注解配置SQL语句?

在MyBatis中,除了利用XML文件配置SQL语句外,还可以利用注解来配置,这样可以减少XML文件的利用,使代码更加简洁。以下为你详细先容如何在MyBatis中利用注解配置SQL语句:
1. 情况预备

起首要确保项目中已经添加了MyBatis的依赖。如果你利用的是Maven项目,可以在pom.xml中添加如下依赖:
  1. <dependency>
  2.     <groupId>org.mybatis</groupId>
  3.     <artifactId>mybatis</artifactId>
  4.     <version>3.5.10</version>
  5. </dependency>
复制代码
2. 定义实体类

定义一个简单的Java实体类,例如User类:
  1. public class User {
  2.     private Integer id;
  3.     private String username;
  4.     private String password;
  5.     // 构造方法、getter和setter方法
  6.     public User() {}
  7.     public User(Integer id, String username, String password) {
  8.         this.id = id;
  9.         this.username = username;
  10.         this.password = password;
  11.     }
  12.     public Integer getId() {
  13.         return id;
  14.     }
  15.     public void setId(Integer id) {
  16.         this.id = id;
  17.     }
  18.     public String getUsername() {
  19.         return username;
  20.     }
  21.     public void setUsername(String username) {
  22.         this.username = username;
  23.     }
  24.     public String getPassword() {
  25.         return password;
  26.     }
  27.     public void setPassword(String password) {
  28.         this.password = password;
  29.     }
  30.     @Override
  31.     public String toString() {
  32.         return "User{" +
  33.                 "id=" + id +
  34.                 ", username='" + username + '\'' +
  35.                 ", password='" + password + '\'' +
  36.                 '}';
  37.     }
  38. }
复制代码
3. 定义Mapper接口并利用注解配置SQL

在Mapper接口中利用MyBatis提供的注解来配置SQL语句。常见的注解有@Select、@Insert、@Update和@Delete 。
  1. import org.apache.ibatis.annotations.*;
  2. import java.util.List;
  3. public interface UserMapper {
  4.     // 查询所有用户
  5.     @Select("SELECT * FROM user")
  6.     List<User> getAllUsers();
  7.     // 根据ID查询用户
  8.     @Select("SELECT * FROM user WHERE id = #{id}")
  9.     User getUserById(int id);
  10.     // 插入用户
  11.     @Insert("INSERT INTO user (username, password) VALUES (#{username}, #{password})")
  12.     @Options(useGeneratedKeys = true, keyProperty = "id")
  13.     int insertUser(User user);
  14.     // 更新用户
  15.     @Update("UPDATE user SET username = #{username}, password = #{password} WHERE id = #{id}")
  16.     int updateUser(User user);
  17.     // 删除用户
  18.     @Delete("DELETE FROM user WHERE id = #{id}")
  19.     int deleteUser(int id);
  20. }
复制代码
注解说明:


4. 配置MyBatis

创建MyBatis的配置文件mybatis-config.xml,并在其中配置数据源和Mapper接口:
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration
  3.         PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4.         "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6.     <environments default="development">
  7.         <environment id="development">
  8.             <transactionManager type="JDBC"/>
  9.             <dataSource type="POOLED">
  10.                 <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
  11.                 <property name="url" value="jdbc:mysql://localhost:3306/your_database"/>
  12.                 <property name="username" value="your_username"/>
  13.                 <property name="password" value="your_password"/>
  14.             </dataSource>
  15.         </environment>
  16.     </environments>
  17.     <mappers>
  18.         <mapper class="com.example.mapper.UserMapper"/>
  19.     </mappers>
  20. </configuration>
复制代码
5. 测试代码

编写测试代码来验证注解配置的SQL语句是否正常工作:
  1. import org.apache.ibatis.io.Resources;
  2. import org.apache.ibatis.session.SqlSession;
  3. import org.apache.ibatis.session.SqlSessionFactory;
  4. import org.apache.ibatis.session.SqlSessionFactoryBuilder;
  5. import java.io.InputStream;
  6. import java.util.List;
  7. public class Main {
  8.     public static void main(String[] args) throws Exception {
  9.         // 加载MyBatis配置文件
  10.         String resource = "mybatis-config.xml";
  11.         InputStream inputStream = Resources.getResourceAsStream(resource);
  12.         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  13.         // 获取SqlSession
  14.         try (SqlSession session = sqlSessionFactory.openSession()) {
  15.             UserMapper userMapper = session.getMapper(UserMapper.class);
  16.             // 查询所有用户
  17.             List<User> users = userMapper.getAllUsers();
  18.             System.out.println("所有用户: " + users);
  19.             // 根据ID查询用户
  20.             User user = userMapper.getUserById(1);
  21.             System.out.println("ID为1的用户: " + user);
  22.             // 插入用户
  23.             User newUser = new User(null, "testUser", "testPassword");
  24.             int rows = userMapper.insertUser(newUser);
  25.             System.out.println("插入用户影响的行数: " + rows);
  26.             System.out.println("新插入用户的ID: " + newUser.getId());
  27.             // 更新用户
  28.             newUser.setUsername("updatedUser");
  29.             rows = userMapper.updateUser(newUser);
  30.             System.out.println("更新用户影响的行数: " + rows);
  31.             // 删除用户
  32.             rows = userMapper.deleteUser(newUser.getId());
  33.             System.out.println("删除用户影响的行数: " + rows);
  34.             session.commit();
  35.         }
  36.     }
  37. }
复制代码
其他复杂注解利用

动态SQL注解

MyBatis还提供了@SelectProvider、@InsertProvider、@UpdateProvider和@DeleteProvider注解,用于实现动态SQL。以下是一个利用@SelectProvider的示例:
  1. import org.apache.ibatis.annotations.Param;
  2. import org.apache.ibatis.annotations.Select;
  3. import org.apache.ibatis.annotations.SelectProvider;
  4. import java.util.List;
  5. public interface UserMapper {
  6.     @SelectProvider(type = UserSqlProvider.class, method = "getUsersByCondition")
  7.     List<User> getUsersByCondition(@Param("username") String username, @Param("password") String password);
  8.     class UserSqlProvider {
  9.         public String getUsersByCondition(String username, String password) {
  10.             StringBuilder sql = new StringBuilder("SELECT * FROM user WHERE 1 = 1");
  11.             if (username != null && !username.isEmpty()) {
  12.                 sql.append(" AND username = #{username}");
  13.             }
  14.             if (password != null && !password.isEmpty()) {
  15.                 sql.append(" AND password = #{password}");
  16.             }
  17.             return sql.toString();
  18.         }
  19.     }
  20. }
复制代码
通过上述步调,你就可以在MyBatis中利用注解来配置SQL语句了。注解配置实用于简单的SQL场景,而对于复杂的SQL,利用XML配置可能会更加清晰和易于维护。
MyBatis-Plus和MyBatis的注解有哪些区别?

MyBatis-Plus 是基于 MyBatis 开发的加强工具,它在生存 MyBatis 注解的基础上,新增了一些注解以提供更便捷的功能。以下为你详细先容两者注解的区别:
共同利用的基础注解

MyBatis-Plus 完全兼容 MyBatis 的基础注解,这些注解在两者中功能和利用方式根本一致:

  1. // MyBatis 和 MyBatis-Plus 中使用方式相同
  2. import org.apache.ibatis.annotations.Select;
  3. public interface UserMapper {
  4.     @Select("SELECT * FROM user WHERE id = #{id}")
  5.     User selectUserById(Long id);
  6. }
复制代码

  1. import org.apache.ibatis.annotations.Insert;
  2. public interface UserMapper {
  3.     @Insert("INSERT INTO user (username, age) VALUES (#{username}, #{age})")
  4.     int insertUser(User user);
  5. }
复制代码

  1. import org.apache.ibatis.annotations.Update;
  2. public interface UserMapper {
  3.     @Update("UPDATE user SET username = #{username} WHERE id = #{id}")
  4.     int updateUser(User user);
  5. }
复制代码

  1. import org.apache.ibatis.annotations.Delete;
  2. public interface UserMapper {
  3.     @Delete("DELETE FROM user WHERE id = #{id}")
  4.     int deleteUserById(Long id);
  5. }
复制代码
MyBatis-Plus 新增的注解

实体类相干注解


  1. import com.baomidou.mybatisplus.annotation.TableName;
  2. @TableName("t_user")
  3. public class User {
  4.     // 类的属性和方法
  5. }
复制代码

  1. import com.baomidou.mybatisplus.annotation.TableId;
  2. import com.baomidou.mybatisplus.annotation.IdType;
  3. public class User {
  4.     @TableId(value = "id", type = IdType.AUTO)
  5.     private Long id;
  6.     // 其他属性和方法
  7. }
复制代码

  1. import com.baomidou.mybatisplus.annotation.TableField;
  2. public class User {
  3.     private String username;
  4.     @TableField(value = "user_age", fill = FieldFill.INSERT)
  5.     private Integer age;
  6.     // 其他属性和方法
  7. }
复制代码

  1. import com.baomidou.mybatisplus.annotation.TableLogic;
  2. public class User {
  3.     private Long id;
  4.     @TableLogic
  5.     private Integer deleted;
  6.     // 其他属性和方法
  7. }
复制代码
代码天生相干注解


  1. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  2. import org.apache.ibatis.annotations.Mapper;
  3. @Mapper
  4. public interface UserMapper extends BaseMapper<User> {
  5.     // 可以自定义扩展方法
  6. }
复制代码
注解利用场景差别


如何在MyBatis中利用注解配置一对多和多对多的关联查询?

在MyBatis中,可以利用注解来配置一对多和多对多的关联查询。以下分别先容这两种关联查询的实现方式:
一对多关联查询

1. 数据库表布局和实体类定义

假设我们有两个表:user(用户表)和order(订单表),一个用户可以有多个订单,这是典范的一对多关系。

  1. -- 用户表
  2. CREATE TABLE user (
  3.     id INT PRIMARY KEY AUTO_INCREMENT,
  4.     username VARCHAR(50)
  5. );
  6. -- 订单表
  7. CREATE TABLE `order` (
  8.     id INT PRIMARY KEY AUTO_INCREMENT,
  9.     order_number VARCHAR(50),
  10.     user_id INT,
  11.     FOREIGN KEY (user_id) REFERENCES user(id)
  12. );
复制代码

  1. import java.util.List;
  2. // 用户实体类
  3. public class User {
  4.     private Integer id;
  5.     private String username;
  6.     private List<Order> orders;
  7.     // 构造方法、getter和setter方法
  8.     public User() {}
  9.     public User(Integer id, String username) {
  10.         this.id = id;
  11.         this.username = username;
  12.     }
  13.     public Integer getId() {
  14.         return id;
  15.     }
  16.     public void setId(Integer id) {
  17.         this.id = id;
  18.     }
  19.     public String getUsername() {
  20.         return username;
  21.     }
  22.     public void setUsername(String username) {
  23.         this.username = username;
  24.     }
  25.     public List<Order> getOrders() {
  26.         return orders;
  27.     }
  28.     public void setOrders(List<Order> orders) {
  29.         this.orders = orders;
  30.     }
  31.     @Override
  32.     public String toString() {
  33.         return "User{" +
  34.                 "id=" + id +
  35.                 ", username='" + username + '\'' +
  36.                 ", orders=" + orders +
  37.                 '}';
  38.     }
  39. }
  40. // 订单实体类
  41. public class Order {
  42.     private Integer id;
  43.     private String orderNumber;
  44.     private Integer userId;
  45.     // 构造方法、getter和setter方法
  46.     public Order() {}
  47.     public Order(Integer id, String orderNumber, Integer userId) {
  48.         this.id = id;
  49.         this.orderNumber = orderNumber;
  50.         this.userId = userId;
  51.     }
  52.     public Integer getId() {
  53.         return id;
  54.     }
  55.     public void setId(Integer id) {
  56.         this.id = id;
  57.     }
  58.     public String getOrderNumber() {
  59.         return orderNumber;
  60.     }
  61.     public void setOrderNumber(String orderNumber) {
  62.         this.orderNumber = orderNumber;
  63.     }
  64.     public Integer getUserId() {
  65.         return userId;
  66.     }
  67.     public void setUserId(Integer userId) {
  68.         this.userId = userId;
  69.     }
  70.     @Override
  71.     public String toString() {
  72.         return "Order{" +
  73.                 "id=" + id +
  74.                 ", orderNumber='" + orderNumber + '\'' +
  75.                 ", userId=" + userId +
  76.                 '}';
  77.     }
  78. }
复制代码
2. Mapper 接口定义及注解配置

  1. import org.apache.ibatis.annotations.*;
  2. import java.util.List;
  3. public interface UserMapper {
  4.     @Results({
  5.             @Result(property = "id", column = "id"),
  6.             @Result(property = "username", column = "username"),
  7.             @Result(property = "orders", column = "id", many = @Many(select = "com.example.mapper.OrderMapper.getOrdersByUserId"))
  8.     })
  9.     @Select("SELECT * FROM user")
  10.     List<User> getAllUsers();
  11. }
  12. public interface OrderMapper {
  13.     @Select("SELECT * FROM `order` WHERE user_id = #{userId}")
  14.     List<Order> getOrdersByUserId(Integer userId);
  15. }
复制代码
注解解释


多对多关联查询

1. 数据库表布局和实体类定义

假设我们有三个表:student(学生表)、course(课程表)和student_course(学生 - 课程关联表),一个学生可以选择多门课程,一门课程也可以被多个学生选择,这是典范的多对多关系。

  1. -- 学生表
  2. CREATE TABLE student (
  3.     id INT PRIMARY KEY AUTO_INCREMENT,
  4.     name VARCHAR(50)
  5. );
  6. -- 课程表
  7. CREATE TABLE course (
  8.     id INT PRIMARY KEY AUTO_INCREMENT,
  9.     course_name VARCHAR(50)
  10. );
  11. -- 学生 - 课程关联表
  12. CREATE TABLE student_course (
  13.     student_id INT,
  14.     course_id INT,
  15.     PRIMARY KEY (student_id, course_id),
  16.     FOREIGN KEY (student_id) REFERENCES student(id),
  17.     FOREIGN KEY (course_id) REFERENCES course(id)
  18. );
复制代码

  1. import java.util.List;
  2. // 学生实体类
  3. public class Student {
  4.     private Integer id;
  5.     private String name;
  6.     private List<Course> courses;
  7.     // 构造方法、getter和setter方法
  8.     public Student() {}
  9.     public Student(Integer id, String name) {
  10.         this.id = id;
  11.         this.name = name;
  12.     }
  13.     public Integer getId() {
  14.         return id;
  15.     }
  16.     public void setId(Integer id) {
  17.         this.id = id;
  18.     }
  19.     public String getName() {
  20.         return name;
  21.     }
  22.     public void setName(String name) {
  23.         this.name = name;
  24.     }
  25.     public List<Course> getCourses() {
  26.         return courses;
  27.     }
  28.     public void setCourses(List<Course> courses) {
  29.         this.courses = courses;
  30.     }
  31.     @Override
  32.     public String toString() {
  33.         return "Student{" +
  34.                 "id=" + id +
  35.                 ", name='" + name + '\'' +
  36.                 ", courses=" + courses +
  37.                 '}';
  38.     }
  39. }
  40. // 课程实体类
  41. public class Course {
  42.     private Integer id;
  43.     private String courseName;
  44.     // 构造方法、getter和setter方法
  45.     public Course() {}
  46.     public Course(Integer id, String courseName) {
  47.         this.id = id;
  48.         this.courseName = courseName;
  49.     }
  50.     public Integer getId() {
  51.         return id;
  52.     }
  53.     public void setId(Integer id) {
  54.         this.id = id;
  55.     }
  56.     public String getCourseName() {
  57.         return courseName;
  58.     }
  59.     public void setCourseName(String courseName) {
  60.         this.courseName = courseName;
  61.     }
  62.     @Override
  63.     public String toString() {
  64.         return "Course{" +
  65.                 "id=" + id +
  66.                 ", courseName='" + courseName + '\'' +
  67.                 '}';
  68.     }
  69. }
复制代码
2. Mapper 接口定义及注解配置

  1. import org.apache.ibatis.annotations.*;
  2. import java.util.List;
  3. public interface StudentMapper {
  4.     @Results({
  5.             @Result(property = "id", column = "id"),
  6.             @Result(property = "name", column = "name"),
  7.             @Result(property = "courses", column = "id", many = @Many(select = "com.example.mapper.CourseMapper.getCoursesByStudentId"))
  8.     })
  9.     @Select("SELECT * FROM student")
  10.     List<Student> getAllStudents();
  11. }
  12. public interface CourseMapper {
  13.     @Select("SELECT c.* FROM course c " +
  14.             "JOIN student_course sc ON c.id = sc.course_id " +
  15.             "WHERE sc.student_id = #{studentId}")
  16.     List<Course> getCoursesByStudentId(Integer studentId);
  17. }
复制代码
注解解释

同样利用 @Results、@Result 和 @Many 注解来处理多对多关系,@Many 中的 select 属性指定调用的另一个 Mapper 方法来查询关联的课程数据。
测试代码

  1. import org.apache.ibatis.io.Resources;
  2. import org.apache.ibatis.session.SqlSession;
  3. import org.apache.ibatis.session.SqlSessionFactory;
  4. import org.apache.ibatis.session.SqlSessionFactoryBuilder;
  5. import java.io.InputStream;
  6. import java.util.List;
  7. public class Main {
  8.     public static void main(String[] args) throws Exception {
  9.         // 加载MyBatis配置文件
  10.         String resource = "mybatis-config.xml";
  11.         InputStream inputStream = Resources.getResourceAsStream(resource);
  12.         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  13.         // 获取SqlSession
  14.         try (SqlSession session = sqlSessionFactory.openSession()) {
  15.             // 一对多查询测试
  16.             UserMapper userMapper = session.getMapper(UserMapper.class);
  17.             List<User> users = userMapper.getAllUsers();
  18.             System.out.println("一对多查询结果:");
  19.             for (User user : users) {
  20.                 System.out.println(user);
  21.             }
  22.             // 多对多查询测试
  23.             StudentMapper studentMapper = session.getMapper(StudentMapper.class);
  24.             List<Student> students = studentMapper.getAllStudents();
  25.             System.out.println("多对多查询结果:");
  26.             for (Student student : students) {
  27.                 System.out.println(student);
  28.             }
  29.         }
  30.     }
  31. }
复制代码
通过以上步调,你可以在 MyBatis 中利用注解配置一对多和多对多的关联查询。
MyBatis-Plus的注解在利用时有哪些须要注意的地方?

1. 实体类注解


2. 逻辑删除注解 @TableLogic

  1. - 要在数据库中设计好逻辑删除字段(通常为 `int` 类型),并设置好默认值和逻辑删除值。
  2. - 配置好全局的逻辑删除字段值,否则可能导致查询结果不符合预期。
复制代码
3. 其他注解

  1. - 在使用自定义 SQL 注解(如 `@SqlParser`)时,要了解其作用和使用场景,避免滥用导致 SQL 执行异常。
复制代码
二、JDBC、MyBatis、MyBatis-Plus 实现 CURD

1. JDBC 实现 CURD

  1. import java.sql.*;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. // 假设数据库中有一个 user 表,包含 id, name, age 字段
  5. class User {
  6.     private int id;
  7.     private String name;
  8.     private int age;
  9.     public User() {}
  10.     public User(int id, String name, int age) {
  11.         this.id = id;
  12.         this.name = name;
  13.         this.age = age;
  14.     }
  15.     // getters and setters
  16.     public int getId() {
  17.         return id;
  18.     }
  19.     public void setId(int id) {
  20.         this.id = id;
  21.     }
  22.     public String getName() {
  23.         return name;
  24.     }
  25.     public void setName(String name) {
  26.         this.name = name;
  27.     }
  28.     public int getAge() {
  29.         return age;
  30.     }
  31.     public void setAge(int age) {
  32.         this.age = age;
  33.     }
  34.     @Override
  35.     public String toString() {
  36.         return "User{id=" + id + ", name='" + name + "', age=" + age + "}";
  37.     }
  38. }
  39. public class JdbcExample {
  40.     private static final String URL = "jdbc:mysql://localhost:3306/test";
  41.     private static final String USER = "root";
  42.     private static final String PASSWORD = "password";
  43.     public static void main(String[] args) {
  44.         // 创建
  45.         createUser(new User(0, "John", 25));
  46.         // 查询
  47.         List<User> users = readUsers();
  48.         for (User user : users) {
  49.             System.out.println(user);
  50.         }
  51.         // 更新
  52.         updateUser(new User(1, "UpdatedJohn", 26));
  53.         // 删除
  54.         deleteUser(1);
  55.     }
  56.     public static void createUser(User user) {
  57.         String sql = "INSERT INTO user (name, age) VALUES (?, ?)";
  58.         try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
  59.              PreparedStatement pstmt = conn.prepareStatement(sql)) {
  60.             pstmt.setString(1, user.getName());
  61.             pstmt.setInt(2, user.getAge());
  62.             pstmt.executeUpdate();
  63.         } catch (SQLException e) {
  64.             e.printStackTrace();
  65.         }
  66.     }
  67.     public static List<User> readUsers() {
  68.         List<User> users = new ArrayList<>();
  69.         String sql = "SELECT * FROM user";
  70.         try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
  71.              Statement stmt = conn.createStatement();
  72.              ResultSet rs = stmt.executeQuery(sql)) {
  73.             while (rs.next()) {
  74.                 int id = rs.getInt("id");
  75.                 String name = rs.getString("name");
  76.                 int age = rs.getInt("age");
  77.                 users.add(new User(id, name, age));
  78.             }
  79.         } catch (SQLException e) {
  80.             e.printStackTrace();
  81.         }
  82.         return users;
  83.     }
  84.     public static void updateUser(User user) {
  85.         String sql = "UPDATE user SET name = ?, age = ? WHERE id = ?";
  86.         try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
  87.              PreparedStatement pstmt = conn.prepareStatement(sql)) {
  88.             pstmt.setString(1, user.getName());
  89.             pstmt.setInt(2, user.getAge());
  90.             pstmt.setInt(3, user.getId());
  91.             pstmt.executeUpdate();
  92.         } catch (SQLException e) {
  93.             e.printStackTrace();
  94.         }
  95.     }
  96.     public static void deleteUser(int id) {
  97.         String sql = "DELETE FROM user WHERE id = ?";
  98.         try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
  99.              PreparedStatement pstmt = conn.prepareStatement(sql)) {
  100.             pstmt.setInt(1, id);
  101.             pstmt.executeUpdate();
  102.         } catch (SQLException e) {
  103.             e.printStackTrace();
  104.         }
  105.     }
  106. }
复制代码
2. MyBatis 实现 CURD

实体类

  1. public class User {
  2.     private int id;
  3.     private String name;
  4.     private int age;
  5.     // getters and setters
  6.     public int getId() {
  7.         return id;
  8.     }
  9.     public void setId(int id) {
  10.         this.id = id;
  11.     }
  12.     public String getName() {
  13.         return name;
  14.     }
  15.     public void setName(String name) {
  16.         this.name = name;
  17.     }
  18.     public int getAge() {
  19.         return age;
  20.     }
  21.     public void setAge(int age) {
  22.         this.age = age;
  23.     }
  24.     @Override
  25.     public String toString() {
  26.         return "User{id=" + id + ", name='" + name + "', age=" + age + "}";
  27.     }
  28. }
复制代码
Mapper 接口

  1. public interface UserMapper {
  2.     void insertUser(User user);
  3.     List<User> selectAllUsers();
  4.     void updateUser(User user);
  5.     void deleteUser(int id);
  6. }
复制代码
Mapper XML 文件(UserMapper.xml)

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3.         PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4.         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="com.example.mapper.UserMapper">
  6.     <insert id="insertUser" parameterType="com.example.entity.User">
  7.         INSERT INTO user (name, age) VALUES (#{name}, #{age})
  8.     </insert>
  9.     <select id="selectAllUsers" resultType="com.example.entity.User">
  10.         SELECT * FROM user
  11.     </select>
  12.     <update id="updateUser" parameterType="com.example.entity.User">
  13.         UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id}
  14.     </update>
  15.     <delete id="deleteUser" parameterType="int">
  16.         DELETE FROM user WHERE id = #{id}
  17.     </delete>
  18. </mapper>
复制代码
测试代码

  1. import org.apache.ibatis.io.Resources;
  2. import org.apache.ibatis.session.SqlSession;
  3. import org.apache.ibatis.session.SqlSessionFactory;
  4. import org.apache.ibatis.session.SqlSessionFactoryBuilder;
  5. import java.io.InputStream;
  6. import java.util.List;
  7. public class MyBatisExample {
  8.     public static void main(String[] args) throws Exception {
  9.         String resource = "mybatis-config.xml";
  10.         InputStream inputStream = Resources.getResourceAsStream(resource);
  11.         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  12.         try (SqlSession session = sqlSessionFactory.openSession()) {
  13.             UserMapper userMapper = session.getMapper(UserMapper.class);
  14.             // 创建
  15.             User user = new User(0, "John", 25);
  16.             userMapper.insertUser(user);
  17.             // 查询
  18.             List<User> users = userMapper.selectAllUsers();
  19.             for (User u : users) {
  20.                 System.out.println(u);
  21.             }
  22.             // 更新
  23.             user.setName("UpdatedJohn");
  24.             user.setAge(26);
  25.             userMapper.updateUser(user);
  26.             // 删除
  27.             userMapper.deleteUser(user.getId());
  28.             session.commit();
  29.         }
  30.     }
  31. }
复制代码
3. MyBatis-Plus 实现 CURD

实体类

  1. import com.baomidou.mybatisplus.annotation.TableName;@TableName("user")public class User {
  2.     private int id;
  3.     private String name;
  4.     private int age;
  5.     // getters and setters
  6.     public int getId() {
  7.         return id;
  8.     }
  9.     public void setId(int id) {
  10.         this.id = id;
  11.     }
  12.     public String getName() {
  13.         return name;
  14.     }
  15.     public void setName(String name) {
  16.         this.name = name;
  17.     }
  18.     public int getAge() {
  19.         return age;
  20.     }
  21.     public void setAge(int age) {
  22.         this.age = age;
  23.     }
  24.     @Override
  25.     public String toString() {
  26.         return "User{id=" + id + ", name='" + name + "', age=" + age + "}";
  27.     }
  28. }
复制代码
Mapper 接口

  1. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  2. public interface UserMapper extends BaseMapper<User> {
  3. }
复制代码
测试代码

  1. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.boot.CommandLineRunner;
  4. import org.springframework.boot.SpringApplication;
  5. import org.springframework.boot.autoconfigure.SpringBootApplication;
  6. import java.util.List;
  7. @SpringBootApplication
  8. public class MyBatisPlusExample implements CommandLineRunner {
  9.     @Autowired
  10.     private UserMapper userMapper;
  11.     public static void main(String[] args) {
  12.         SpringApplication.run(MyBatisPlusExample.class, args);
  13.     }
  14.     @Override
  15.     public void run(String... args) throws Exception {
  16.         // 创建
  17.         User user = new User();
  18.         user.setName("John");
  19.         user.setAge(25);
  20.         userMapper.insert(user);
  21.         // 查询
  22.         QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  23.         List<User> users = userMapper.selectList(queryWrapper);
  24.         for (User u : users) {
  25.             System.out.println(u);
  26.         }
  27.         // 更新
  28.         user.setName("UpdatedJohn");
  29.         user.setAge(26);
  30.         userMapper.updateById(user);
  31.         // 删除
  32.         userMapper.deleteById(user.getId());
  33.     }
  34. }
复制代码
三、MyBatis 实现一对多查询

数据库表布局

假设存在 user 表和 order 表,一个用户可以有多个订单,表布局如下:
  1. -- 用户表
  2. CREATE TABLE user (
  3.     id INT PRIMARY KEY AUTO_INCREMENT,
  4.     name VARCHAR(50)
  5. );
  6. -- 订单表
  7. CREATE TABLE `order` (
  8.     id INT PRIMARY KEY AUTO_INCREMENT,
  9.     order_number VARCHAR(50),
  10.     user_id INT,
  11.     FOREIGN KEY (user_id) REFERENCES user(id)
  12. );
复制代码
实体类

  1. import java.util.List;
  2. public class User {
  3.     private int id;
  4.     private String name;
  5.     private List<Order> orders;
  6.     // getters and setters
  7.     public int getId() {
  8.         return id;
  9.     }
  10.     public void setId(int id) {
  11.         this.id = id;
  12.     }
  13.     public String getName() {
  14.         return name;
  15.     }
  16.     public void setName(String name) {
  17.         this.name = name;
  18.     }
  19.     public List<Order> getOrders() {
  20.         return orders;
  21.     }
  22.     public void setOrders(List<Order> orders) {
  23.         this.orders = orders;
  24.     }
  25.     @Override
  26.     public String toString() {
  27.         return "User{id=" + id + ", name='" + name + "', orders=" + orders + "}";
  28.     }
  29. }
  30. public class Order {
  31.     private int id;
  32.     private String orderNumber;
  33.     private int userId;
  34.     // getters and setters
  35.     public int getId() {
  36.         return id;
  37.     }
  38.     public void setId(int id) {
  39.         this.id = id;
  40.     }
  41.     public String getOrderNumber() {
  42.         return orderNumber;
  43.     }
  44.     public void setOrderNumber(String orderNumber) {
  45.         this.orderNumber = orderNumber;
  46.     }
  47.     public int getUserId() {
  48.         return userId;
  49.     }
  50.     public void setUserId(int userId) {
  51.         this.userId = userId;
  52.     }
  53.     @Override
  54.     public String toString() {
  55.         return "Order{id=" + id + ", orderNumber='" + orderNumber + "', userId=" + userId + "}";
  56.     }
  57. }
复制代码
Mapper 接口

  1. public interface UserMapper {
  2.     List<User> selectAllUsersWithOrders();
  3. }
复制代码
Mapper XML 文件(UserMapper.xml)

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3.         PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4.         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="com.example.mapper.UserMapper">
  6.     <resultMap id="UserWithOrdersResultMap" type="com.example.entity.User">
  7.         <id property="id" column="user_id"/>
  8.         <result property="name" column="user_name"/>
  9.         <collection property="orders" ofType="com.example.entity.Order">
  10.             <id property="id" column="order_id"/>
  11.             <result property="orderNumber" column="order_number"/>
  12.             <result property="userId" column="user_id"/>
  13.         </collection>
  14.     </resultMap>
  15.     <select id="selectAllUsersWithOrders" resultMap="UserWithOrdersResultMap">
  16.         SELECT u.id AS user_id, u.name AS user_name, o.id AS order_id, o.order_number
  17.         FROM user u
  18.         LEFT JOIN `order` o ON u.id = o.user_id
  19.     </select>
  20. </mapper>
复制代码
测试代码

  1. import org.apache.ibatis.io.Resources;
  2. import org.apache.ibatis.session.SqlSession;
  3. import org.apache.ibatis.session.SqlSessionFactory;
  4. import org.apache.ibatis.session.SqlSessionFactoryBuilder;
  5. import java.io.InputStream;
  6. import java.util.List;
  7. public class OneToManyExample {
  8.     public static void main(String[] args) throws Exception {
  9.         String resource = "mybatis-config.xml";
  10.         InputStream inputStream = Resources.getResourceAsStream(resource);
  11.         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  12.         try (SqlSession session = sqlSessionFactory.openSession()) {
  13.             UserMapper userMapper = session.getMapper(UserMapper.class);
  14.             List<User> users = userMapper.selectAllUsersWithOrders();
  15.             for (User user : users) {
  16.                 System.out.println(user);
  17.             }
  18.         }
  19.     }
  20. }
复制代码
以上代码展示了 JDBC、MyBatis、MyBatis-Plus 实现 CURD 以及 MyBatis 实现一对多查询的详细过程。注意,运行代码前须要确保数据库毗连配置精确,而且添加相应的依赖(如 MyBatis、MyBatis-Plus、JDBC 驱动等)。
MyBatis-Plus 实现一对多

以下将详细先容如何利用 MyBatis-Plus 联合 mapper.xml 实现一对多查询,仍以用户(User)和订单(Order)的一对多关系为例。
1. 数据库表布局

  1. -- 用户表
  2. CREATE TABLE user (
  3.     id INT PRIMARY KEY AUTO_INCREMENT,
  4.     username VARCHAR(50)
  5. );
  6. -- 订单表
  7. CREATE TABLE `order` (
  8.     id INT PRIMARY KEY AUTO_INCREMENT,
  9.     order_number VARCHAR(50),
  10.     user_id INT,
  11.     FOREIGN KEY (user_id) REFERENCES user(id)
  12. );
复制代码
2. 实体类定义

  1. // User.java
  2. public class User {
  3.     private Integer id;
  4.     private String username;
  5.     private List<Order> orders;
  6.     public Integer getId() {
  7.         return id;
  8.     }
  9.     public void setId(Integer id) {
  10.         this.id = id;
  11.     }
  12.     public String getUsername() {
  13.         return username;
  14.     }
  15.     public void setUsername(String username) {
  16.         this.username = username;
  17.     }
  18.     public List<Order> getOrders() {
  19.         return orders;
  20.     }
  21.     public void setOrders(List<Order> orders) {
  22.         this.orders = orders;
  23.     }
  24. }
  25. // Order.java
  26. public class Order {
  27.     private Integer id;
  28.     private String orderNumber;
  29.     private Integer userId;
  30.     public Integer getId() {
  31.         return id;
  32.     }
  33.     public void setId(Integer id) {
  34.         this.id = id;
  35.     }
  36.     public String getOrderNumber() {
  37.         return orderNumber;
  38.     }
  39.     public void setOrderNumber(String orderNumber) {
  40.         this.orderNumber = orderNumber;
  41.     }
  42.     public Integer getUserId() {
  43.         return userId;
  44.     }
  45.     public void setUserId(Integer userId) {
  46.         this.userId = userId;
  47.     }
  48. }
复制代码
3. Mapper 接口定义

  1. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  2. // UserMapper.java
  3. public interface UserMapper extends BaseMapper<User> {
  4.     /**
  5.      * 查询所有用户及其关联的订单
  6.      * @return 用户列表
  7.      */
  8.     List<User> getAllUsersWithOrders();
  9. }
复制代码
4. Mapper XML 文件(UserMapper.xml)

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3.         PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4.         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="com.example.mapper.UserMapper">
  6.     <!-- 定义结果映射 -->
  7.     <resultMap id="UserWithOrdersResultMap" type="com.example.entity.User">
  8.         <id property="id" column="user_id"/>
  9.         <result property="username" column="username"/>
  10.         <!-- 一对多关联,使用 collection 标签 -->
  11.         <collection property="orders" ofType="com.example.entity.Order">
  12.             <id property="id" column="order_id"/>
  13.             <result property="orderNumber" column="order_number"/>
  14.             <result property="userId" column="user_id"/>
  15.         </collection>
  16.     </resultMap>
  17.     <!-- 查询所有用户及其关联的订单 -->
  18.     <select id="getAllUsersWithOrders" resultMap="UserWithOrdersResultMap">
  19.         SELECT
  20.             u.id AS user_id,
  21.             u.username,
  22.             o.id AS order_id,
  23.             o.order_number
  24.         FROM
  25.             user u
  26.         LEFT JOIN
  27.             `order` o ON u.id = o.user_id
  28.     </select>
  29. </mapper>
复制代码
5. Service 层实现

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.stereotype.Service;
  3. import java.util.List;
  4. // UserService.java
  5. @Service
  6. public class UserService {
  7.     @Autowired
  8.     private UserMapper userMapper;
  9.     public List<User> getUsersWithOrders() {
  10.         return userMapper.getAllUsersWithOrders();
  11.     }
  12. }
复制代码
6. 测试代码

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.boot.CommandLineRunner;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import java.util.List;
  6. // Application.java
  7. @SpringBootApplication
  8. public class Application implements CommandLineRunner {
  9.     @Autowired
  10.     private UserService userService;
  11.     public static void main(String[] args) {
  12.         SpringApplication.run(Application.class, args);
  13.     }
  14.     @Override
  15.     public void run(String... args) throws Exception {
  16.         List<User> users = userService.getUsersWithOrders();
  17.         for (User user : users) {
  18.             System.out.println("User: " + user.getUsername());
  19.             List<Order> orders = user.getOrders();
  20.             if (orders != null) {
  21.                 for (Order order : orders) {
  22.                     System.out.println("  Order: " + order.getOrderNumber());
  23.                 }
  24.             }
  25.         }
  26.     }
  27. }
复制代码
代码解释


通过以上步调,你可以利用 MyBatis-Plus 联合 mapper.xml 实现一对多查询。
JDBC、MyBatis、MyBatis-Plus是Java开发中常用的数据库利用技术,以下是它们的架构图、底层原理和底层实现的相干先容:

JDBC


MyBatis


MyBatis-Plus


JDBC、MyBatis、MyBatis-Plus 中的事务分别是什么?怎么实现的给出代码或者sql脚本? 底层原理是什么?

1. JDBC 中的事务

事务概念

在 JDBC 中,事务是一组不可分割的数据库利用序列,这些利用要么全部乐成执行并持久化到数据库(提交),要么在出现错误时全部打消(回滚),以此保证数据的一致性和完整性。
实现代码

  1. import java.sql.Connection;
  2. import java.sql.DriverManager;
  3. import java.sql.SQLException;
  4. import java.sql.Statement;
  5. public class JDBCTransactionExample {
  6.     private static final String URL = "jdbc:mysql://localhost:3306/your_database";
  7.     private static final String USER = "your_username";
  8.     private static final String PASSWORD = "your_password";
  9.     public static void main(String[] args) {
  10.         Connection connection = null;
  11.         Statement statement = null;
  12.         try {
  13.             // 加载驱动
  14.             Class.forName("com.mysql.cj.jdbc.Driver");
  15.             // 获取连接
  16.             connection = DriverManager.getConnection(URL, USER, PASSWORD);
  17.             // 关闭自动提交,开启事务
  18.             connection.setAutoCommit(false);
  19.             statement = connection.createStatement();
  20.             // 执行一系列 SQL 操作
  21.             String sql1 = "INSERT INTO account (id, balance) VALUES (1, 1000)";
  22.             statement.executeUpdate(sql1);
  23.             String sql2 = "UPDATE account SET balance = balance - 500 WHERE id = 1";
  24.             statement.executeUpdate(sql2);
  25.             // 提交事务
  26.             connection.commit();
  27.             System.out.println("事务提交成功");
  28.         } catch (Exception e) {
  29.             // 出现异常,回滚事务
  30.             if (connection != null) {
  31.                 try {
  32.                     connection.rollback();
  33.                     System.out.println("事务回滚成功");
  34.                 } catch (SQLException ex) {
  35.                     ex.printStackTrace();
  36.                 }
  37.             }
  38.             e.printStackTrace();
  39.         } finally {
  40.             // 关闭资源
  41.             try {
  42.                 if (statement != null) statement.close();
  43.                 if (connection != null) connection.close();
  44.             } catch (SQLException e) {
  45.                 e.printStackTrace();
  46.             }
  47.         }
  48.     }
  49. }
复制代码
底层原理

JDBC 事务的底层原理基于数据库本身的事务管理机制。当调用 connection.setAutoCommit(false) 时,JDBC 驱动会关照数据库服务器开始一个新的事务。之后执行的 SQL 语句都在这个事务的上下文中进行。当调用 connection.commit() 时,JDBC 驱动会向数据库服务器发送提交事务的指令,数据库服务器将事务中全部利用的结果持久化到磁盘;当调用 connection.rollback() 时,数据库服务器会打消事务中已经执行的利用。
2. MyBatis 中的事务

事务概念

MyBatis 中的事务和 JDBC 事务本质雷同,是为了确保一组数据库利用的原子性、一致性、隔离性和持久性(ACID 特性)。
实现代码

以下是利用 MyBatis 手动控制事务的示例:
  1. import org.apache.ibatis.io.Resources;
  2. import org.apache.ibatis.session.SqlSession;
  3. import org.apache.ibatis.session.SqlSessionFactory;
  4. import org.apache.ibatis.session.SqlSessionFactoryBuilder;
  5. import java.io.IOException;
  6. import java.io.InputStream;
  7. public class MyBatisTransactionExample {
  8.     public static void main(String[] args) {
  9.         SqlSession sqlSession = null;
  10.         try {
  11.             // 加载 MyBatis 配置文件
  12.             String resource = "mybatis-config.xml";
  13.             InputStream inputStream = Resources.getResourceAsStream(resource);
  14.             SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  15.             // 获取 SqlSession,关闭自动提交,开启事务
  16.             sqlSession = sqlSessionFactory.openSession(false);
  17.             // 执行数据库操作,假设存在一个 UserMapper 接口和对应的方法
  18.             // UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  19.             // userMapper.insertUser(new User());
  20.             // userMapper.updateUser(new User());
  21.             // 提交事务
  22.             sqlSession.commit();
  23.             System.out.println("事务提交成功");
  24.         } catch (IOException e) {
  25.             e.printStackTrace();
  26.         } catch (Exception e) {
  27.             // 出现异常,回滚事务
  28.             if (sqlSession != null) {
  29.                 sqlSession.rollback();
  30.                 System.out.println("事务回滚成功");
  31.             }
  32.             e.printStackTrace();
  33.         } finally {
  34.             // 关闭 SqlSession
  35.             if (sqlSession != null) {
  36.                 sqlSession.close();
  37.             }
  38.         }
  39.     }
  40. }
复制代码
底层原理

MyBatis 事务底层依赖于 JDBC 事务。MyBatis 的 SqlSession 持有一个 Connection 对象,当调用 sqlSessionFactory.openSession(false) 时,会将 Connection 的自动提交模式关闭,开启一个新的事务。在事务执行过程中,MyBatis 通过 StatementHandler、ParameterHandler 等组件将 SQL 语句发送到数据库执行。当调用 sqlSession.commit() 或 sqlSession.rollback() 时,实际上是调用 Connection 的 commit() 或 rollback() 方法来提交或回滚事务。
3. MyBatis-Plus 中的事务

事务概念

MyBatis-Plus 是基于 MyBatis 的加强工具,其事务概念和 MyBatis 一致,用于保证数据库利用的一致性和完整性。
实现代码

通常联合 Spring Boot 和 Spring 的事务管理,利用 @Transactional 注解来实现事务控制。
  1. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.stereotype.Service;
  4. import org.springframework.transaction.annotation.Transactional;
  5. import java.util.List;
  6. @Service
  7. public class UserService {
  8.     @Autowired
  9.     private UserMapper userMapper;
  10.     @Transactional
  11.     public void doTransaction() {
  12.         // 执行数据库操作
  13.         User user = new User();
  14.         user.setName("John");
  15.         userMapper.insert(user);
  16.         // 模拟异常
  17.         int result = 1 / 0;
  18.         QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  19.         queryWrapper.eq("name", "John");
  20.         List<User> users = userMapper.selectList(queryWrapper);
  21.         for (User u : users) {
  22.             u.setName("Updated John");
  23.             userMapper.updateById(u);
  24.         }
  25.     }
  26. }
复制代码
底层原理

MyBatis-Plus 借助 Spring 的事务管理机制,通过 Spring AOP(面向切面编程)实现事务控制。当方法上添加了 @Transactional 注解时,Spring 在方法执行前会创建一个事务上下文,获取 DataSourceTransactionManager 管理的 Connection,并将其绑定到当前线程。在方法执行过程中,MyBatis-Plus 的数据库利用会在这个事务上下文中进行。如果方法正常执行完毕,Spring 的事务管理器会调用 Connection 的 commit() 方法提交事务;如果方法抛出异常,事务管理器会根据异常类型和 @Transactional 注解的配置,调用 Connection 的 rollback() 方法回滚事务。
JDBC、MyBatis、MyBatis-Plus怎么保证幂等性

你想问的可能是“JDBC、MyBatis、MyBatis - Plus 怎么保证幂等性”。幂等性是指一个利用无论执行多少次,所产生的影响都和执行一次的影响雷同。下面分别先容这三种技术保证幂等性的方法:
JDBC 保证幂等性

1. 唯一索引

在数据库表中创建唯一索引,例如在插入利用时,如果存在唯一索引,重复插入雷同的数据会导致数据库抛出唯一束缚辩说异常,从而避免重复插入。
  1. import java.sql.Connection;
  2. import java.sql.DriverManager;
  3. import java.sql.PreparedStatement;
  4. import java.sql.SQLException;
  5. public class JDBCIdempotencyExample {
  6.     private static final String URL = "jdbc:mysql://localhost:3306/your_database";
  7.     private static final String USER = "your_username";
  8.     private static final String PASSWORD = "your_password";
  9.     public static void main(String[] args) {
  10.         try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) {
  11.             // 假设表中有一个唯一索引在 username 字段上
  12.             String sql = "INSERT INTO users (username, email) VALUES (?, ?)";
  13.             try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
  14.                 preparedStatement.setString(1, "unique_user");
  15.                 preparedStatement.setString(2, "user@example.com");
  16.                 try {
  17.                     preparedStatement.executeUpdate();
  18.                     System.out.println("插入成功");
  19.                 } catch (SQLException e) {
  20.                     if (e.getSQLState().equals("23000")) {
  21.                         System.out.println("由于唯一约束,插入失败,数据可能已存在");
  22.                     } else {
  23.                         e.printStackTrace();
  24.                     }
  25.                 }
  26.             }
  27.         } catch (SQLException e) {
  28.             e.printStackTrace();
  29.         }
  30.     }
  31. }
复制代码
2. 查询 - 插入模式

在执行插入利用之前,先查询数据是否已经存在,如果存在则不进行插入利用。
  1. import java.sql.Connection;
  2. import java.sql.DriverManager;
  3. import java.sql.PreparedStatement;
  4. import java.sql.ResultSet;
  5. import java.sql.SQLException;
  6. public class JDBCQueryInsertExample {
  7.     private static final String URL = "jdbc:mysql://localhost:3306/your_database";
  8.     private static final String USER = "your_username";
  9.     private static final String PASSWORD = "your_password";
  10.     public static void main(String[] args) {
  11.         try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) {
  12.             // 查询数据是否存在
  13.             String querySql = "SELECT id FROM users WHERE username = ?";
  14.             try (PreparedStatement queryStatement = connection.prepareStatement(querySql)) {
  15.                 queryStatement.setString(1, "unique_user");
  16.                 try (ResultSet resultSet = queryStatement.executeQuery()) {
  17.                     if (!resultSet.next()) {
  18.                         // 数据不存在,进行插入操作
  19.                         String insertSql = "INSERT INTO users (username, email) VALUES (?, ?)";
  20.                         try (PreparedStatement insertStatement = connection.prepareStatement(insertSql)) {
  21.                             insertStatement.setString(1, "unique_user");
  22.                             insertStatement.setString(2, "user@example.com");
  23.                             insertStatement.executeUpdate();
  24.                             System.out.println("插入成功");
  25.                         }
  26.                     } else {
  27.                         System.out.println("数据已存在,不进行插入");
  28.                     }
  29.                 }
  30.             }
  31.         } catch (SQLException e) {
  32.             e.printStackTrace();
  33.         }
  34.     }
  35. }
复制代码
MyBatis 保证幂等性

1. 利用唯一索引和异常处理

和 JDBC 类似,利用数据库的唯一索引,在 MyBatis 的 Mapper 接口方法中捕捉唯一束缚辩说异常进行处理。
  1. import org.apache.ibatis.exceptions.PersistenceException;
  2. import org.apache.ibatis.session.SqlSession;
  3. import org.apache.ibatis.session.SqlSessionFactory;
  4. import org.apache.ibatis.session.SqlSessionFactoryBuilder;
  5. import java.io.InputStream;
  6. public class MyBatisIdempotencyExample {
  7.     public static void main(String[] args) {
  8.         String resource = "mybatis-config.xml";
  9.         try (InputStream inputStream = org.apache.ibatis.io.Resources.getResourceAsStream(resource)) {
  10.             SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  11.             try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
  12.                 UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  13.                 User user = new User();
  14.                 user.setUsername("unique_user");
  15.                 user.setEmail("user@example.com");
  16.                 try {
  17.                     userMapper.insertUser(user);
  18.                     sqlSession.commit();
  19.                     System.out.println("插入成功");
  20.                 } catch (PersistenceException e) {
  21.                     if (e.getCause() instanceof java.sql.SQLIntegrityConstraintViolationException) {
  22.                         System.out.println("由于唯一约束,插入失败,数据可能已存在");
  23.                     } else {
  24.                         e.printStackTrace();
  25.                     }
  26.                 }
  27.             }
  28.         } catch (Exception e) {
  29.             e.printStackTrace();
  30.         }
  31.     }
  32. }
复制代码
2. 查询 - 插入逻辑

在 Service 层实现查询 - 插入的逻辑。
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.stereotype.Service;
  3. @Service
  4. public class UserService {
  5.     @Autowired
  6.     private UserMapper userMapper;
  7.     public void insertUserIfNotExists(User user) {
  8.         User existingUser = userMapper.selectUserByUsername(user.getUsername());
  9.         if (existingUser == null) {
  10.             userMapper.insertUser(user);
  11.         }
  12.     }
  13. }
复制代码
MyBatis - Plus 保证幂等性

1. 利用唯一索引和异常处理

MyBatis - Plus 基于 MyBatis,同样可以利用数据库的唯一索引,在调用 MyBatis - Plus 提供的插入方法时捕捉唯一束缚辩说异常。
  1. import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
  2. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.stereotype.Service;
  5. @Service
  6. public class UserService {
  7.     @Autowired
  8.     private UserMapper userMapper;
  9.     public void insertUser(User user) {
  10.         try {
  11.             userMapper.insert(user);
  12.         } catch (MybatisPlusException e) {
  13.             if (e.getCause() instanceof java.sql.SQLIntegrityConstraintViolationException) {
  14.                 System.out.println("由于唯一约束,插入失败,数据可能已存在");
  15.             } else {
  16.                 e.printStackTrace();
  17.             }
  18.         }
  19.     }
  20. }
复制代码
2. 利用 saveOrUpdate 方法

MyBatis - Plus 提供了 saveOrUpdate 方法,该方法会根据实体的主键是否存在来决定是执行插入还是更新利用,一定程度上保证了幂等性。
  1. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  2. import org.springframework.stereotype.Service;
  3. @Service
  4. public class UserServiceImpl extends ServiceImpl<UserMapper, User> {
  5.     public void saveOrUpdateUser(User user) {
  6.         this.saveOrUpdate(user);
  7.     }
  8. }
复制代码
3. 分布式锁和令牌机制(实用于高并发场景)

在高并发场景下,可以利用分布式锁(如 Redis 分布式锁)或令牌机制来保证利用的幂等性。例如,在执行利用前,先从 Redis 中获取一个唯一的令牌,利用完成后将令牌存储在 Redis 中,后续再次哀求时先检查令牌是否已存在,如果存在则不执行利用。
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.data.redis.core.RedisTemplate;
  3. import org.springframework.stereotype.Service;
  4. import java.util.UUID;
  5. @Service
  6. public class UserService {
  7.     @Autowired
  8.     private UserMapper userMapper;
  9.     @Autowired
  10.     private RedisTemplate<String, String> redisTemplate;
  11.     public void insertUserWithIdempotency(User user) {
  12.         String token = UUID.randomUUID().toString();
  13.         if (redisTemplate.opsForValue().setIfAbsent(token, "processed")) {
  14.             try {
  15.                 userMapper.insert(user);
  16.             } finally {
  17.                 redisTemplate.delete(token);
  18.             }
  19.         } else {
  20.             System.out.println("操作已执行,避免重复操作");
  21.         }
  22.     }
  23. }
复制代码
以上就是 JDBC、MyBatis、MyBatis - Plus 保证幂等性的常见方法,不同的场景可以选择符合的方式来确保利用的幂等性。
JDBC、MyBatis 和 MyBatis - Plus 的事务利用本质上是基于数据库事务

是的,你的明白是精确的。JDBC、MyBatis 和 MyBatis - Plus 的事务利用本质上是基于数据库事务的,若数据库本身不支持事务,这些框架即便有利用事务的方法和功能,也无法保证事务的特性。下面为你详细解释:
本质依赖数据库事务


数据库不支持事务时的情况


综上所述,JDBC、MyBatis 和 MyBatis - Plus 的事务利用依赖于数据库的事务支持,数据库的事务功能是实现这些框架事务特性的基础。
JDBC、MyBatis 和 MyBatis - Plus 怎么实现逻辑删除?

逻辑删除是指在数据库中并不真正删除数据,而是通过设置一个标记位来表示该数据已被删除,这样可以避免物理删除带来的数据丢失风险,方便后续的数据规复和审计。以下分别先容 JDBC、MyBatis 和 MyBatis - Plus 实现逻辑删除的方法:
JDBC 实现逻辑删除

1. 数据库表计划

在须要进行逻辑删除的表中添加一个逻辑删除标记字段,例如 is_deleted,通常利用 0 表示未删除,1 表示已删除。
  1. CREATE TABLE users (
  2.     id INT PRIMARY KEY AUTO_INCREMENT,
  3.     username VARCHAR(50),
  4.     is_deleted TINYINT(1) DEFAULT 0
  5. );
复制代码
2. Java 代码实现

在执行删除利用时,将 is_deleted 字段更新为 1,而不是真正删除记载。在查询数据时,过滤掉 is_deleted 为 1 的记载。
  1. import java.sql.Connection;
  2. import java.sql.DriverManager;
  3. import java.sql.PreparedStatement;
  4. import java.sql.ResultSet;
  5. import java.sql.SQLException;
  6. public class JDBCLogicDeleteExample {
  7.     private static final String URL = "jdbc:mysql://localhost:3306/your_database";
  8.     private static final String USER = "your_username";
  9.     private static final String PASSWORD = "your_password";
  10.     public static void main(String[] args) {
  11.         // 逻辑删除操作
  12.         try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) {
  13.             String deleteSql = "UPDATE users SET is_deleted = 1 WHERE id = ?";
  14.             try (PreparedStatement preparedStatement = connection.prepareStatement(deleteSql)) {
  15.                 preparedStatement.setInt(1, 1);
  16.                 preparedStatement.executeUpdate();
  17.                 System.out.println("逻辑删除成功");
  18.             }
  19.         } catch (SQLException e) {
  20.             e.printStackTrace();
  21.         }
  22.         // 查询未删除的数据
  23.         try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) {
  24.             String selectSql = "SELECT * FROM users WHERE is_deleted = 0";
  25.             try (PreparedStatement preparedStatement = connection.prepareStatement(selectSql);
  26.                  ResultSet resultSet = preparedStatement.executeQuery()) {
  27.                 while (resultSet.next()) {
  28.                     System.out.println("ID: " + resultSet.getInt("id") + ", Username: " + resultSet.getString("username"));
  29.                 }
  30.             }
  31.         } catch (SQLException e) {
  32.             e.printStackTrace();
  33.         }
  34.     }
  35. }
复制代码
MyBatis 实现逻辑删除

1. 数据库表计划

同样在表中添加 is_deleted 字段。
  1. CREATE TABLE users (
  2.     id INT PRIMARY KEY AUTO_INCREMENT,
  3.     username VARCHAR(50),
  4.     is_deleted TINYINT(1) DEFAULT 0
  5. );
复制代码
2. 实体类定义

在实体类中添加 isDeleted 属性,并利用 @TableLogic 注解(如果利用 MyBatis - Plus 注解支持)或在 SQL 中手动处理。
  1. import lombok.Data;
  2. @Data
  3. public class User {
  4.     private Integer id;
  5.     private String username;
  6.     private Integer isDeleted;
  7. }
复制代码
3. Mapper 接口和 XML 文件

在 Mapper 接口中定义逻辑删除和查询方法,在 XML 文件中编写相应的 SQL 语句。
  1. import org.apache.ibatis.annotations.Mapper;
  2. @Mapper
  3. public interface UserMapper {
  4.     int logicDeleteById(Integer id);
  5. }
复制代码
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3.         PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4.         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="com.example.mapper.UserMapper">
  6.     <update id="logicDeleteById">
  7.         UPDATE users
  8.         SET is_deleted = 1
  9.         WHERE id = #{id}
  10.     </update>
  11.     <select id="selectAllUsers" resultType="com.example.entity.User">
  12.         SELECT * FROM users WHERE is_deleted = 0
  13.     </select>
  14. </mapper>
复制代码
MyBatis - Plus 实现逻辑删除

1. 数据库表计划

添加 is_deleted 字段。
  1. CREATE TABLE users (
  2.     id INT PRIMARY KEY AUTO_INCREMENT,
  3.     username VARCHAR(50),
  4.     is_deleted TINYINT(1) DEFAULT 0
  5. );
复制代码
2. 实体类定义

利用 @TableLogic 注解标记逻辑删除字段。
  1. import com.baomidou.mybatisplus.annotation.TableLogic;
  2. import lombok.Data;
  3. @Data
  4. public class User {
  5.     private Integer id;
  6.     private String username;
  7.     @TableLogic
  8.     private Integer isDeleted;
  9. }
复制代码
3. 配置逻辑删除规则

在配置类中配置逻辑删除的规则。
  1. import com.baomidou.mybatisplus.core.config.GlobalConfig;
  2. import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
  3. import com.baomidou.mybatisplus.core.injector.ISqlInjector;
  4. import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
  5. import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
  6. import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.Configuration;
  9. @Configuration
  10. public class MyBatisPlusConfig {
  11.     @Bean
  12.     public GlobalConfig globalConfig() {
  13.         GlobalConfig globalConfig = new GlobalConfig();
  14.         GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig();
  15.         dbConfig.setLogicDeleteField("is_deleted");
  16.         dbConfig.setLogicNotDeleteValue("0");
  17.         dbConfig.setLogicDeleteValue("1");
  18.         globalConfig.setDbConfig(dbConfig);
  19.         return globalConfig;
  20.     }
  21. }
复制代码
4. 利用 MyBatis - Plus 进行逻辑删除和查询

  1. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  2. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  3. import org.springframework.stereotype.Service;
  4. @Service
  5. public class UserService extends ServiceImpl<UserMapper, User> {
  6.     public void logicDeleteUser(Integer id) {
  7.         this.removeById(id); // 调用 MyBatis - Plus 的 removeById 方法进行逻辑删除
  8.     }
  9.     public void selectAllUsers() {
  10.         QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  11.         List<User> users = this.list(queryWrapper); // 查询未删除的数据
  12.         for (User user : users) {
  13.             System.out.println("ID: " + user.getId() + ", Username: " + user.getUsername());
  14.         }
  15.     }
  16. }
复制代码
以上就是 JDBC、MyBatis 和 MyBatis - Plus 实现逻辑删除的方法,其中 MyBatis - Plus 提供了更便捷的方式来实现逻辑删除,减少了开发职员的工作量。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 qidao123.com技术社区-IT企服评测·应用市场 (https://dis.qidao123.com/) Powered by Discuz! X3.4