Mybatis+JPA

深入:MyBatis详解 - 总体框架设计 | Java 全栈知识体系 (pdai.tech)

什么是MyBatis?

  • MyBatis是一款优秀的持久层框架
  • MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的过程,减少了代码的冗余,减少程序员的操作。
  • MyBatis 可以使用简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO (Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录

JDBC

public class JDBCDemo {
    public static void main(String[] args) {
        String jdbcUrl = "jdbc:mysql://localhost:3306/testdb";
        String username = "root";
        String password = "password";
        
        try {
            // 加载JDBC驱动程序
            Class.forName("com.mysql.cj.jdbc.Driver");
            
            // 建立数据库连接
            Connection connection = DriverManager.getConnection(jdbcUrl, username, password);
            
            // 创建Statement对象
            Statement statement = connection.createStatement();
            
            // 执行查询语句
            ResultSet resultSet = statement.executeQuery("SELECT * FROM users");
            
            // 处理结果集
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                System.out.println("ID: " + id + ", Name: " + name);
            }
            
            // 使用PreparedStatement执行带参数的查询
            String sql = "SELECT * FROM users WHERE id = ?";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1, 1);
            ResultSet preparedStatementResultSet = preparedStatement.executeQuery();
            
            // 处理结果集
            if (preparedStatementResultSet.next()) {
                int id = preparedStatementResultSet.getInt("id");
                String name = preparedStatementResultSet.getString("name");
                System.out.println("ID: " + id + ", Name: " + name);
            }
            
            // 关闭资源
            resultSet.close();
            preparedStatementResultSet.close();
            statement.close();
            preparedStatement.close();
            connection.close();
            
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
    }
}

使用 JDBC 访问数据库通常分为四个主要步骤。这些步骤适用于大多数的 JDBC 操作,无论是查询、插入、更新还是删除数据。

1. 加载数据库驱动程序

第一步是加载数据库的 JDBC 驱动程序。驱动程序是数据库与 Java 之间的桥梁,它使得 Java 能够与特定类型的数据库进行通信。

Class.forName("com.mysql.cj.jdbc.Driver");

对于现代的 JDBC,尤其是在使用 JDBC 4.0 及以后的版本时,这一步是可选的,因为 JDBC 会自动检测并加载驱动程序。

2. 创建数据库连接

第二步是使用 DriverManager 获取一个 Connection 对象,该对象表示与数据库的连接。你需要提供数据库的 URL、用户名和密码。

String url = "jdbc:mysql://localhost:3306/your_database";
String username = "your_username";
String password = "your_password";

Connection connection = DriverManager.getConnection(url, username, password);

3. 创建并执行 SQL 语句

接下来,需要创建一个 StatementPreparedStatement 对象,并通过它执行 SQL 语句。你可以执行查询(SELECT)或更新(INSERTUPDATEDELETE)操作。

String sql = "SELECT * FROM users";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);

4. 处理结果集和关闭资源

最后一步是处理查询的结果,并关闭所有的 JDBC 资源,包括 ResultSetStatementConnection。为了避免资源泄漏,建议在完成操作后及时关闭 ResultSetStatementConnection。使用 try-with-resources 语句可以简化资源管理:

try (Connection connection = DriverManager.getConnection(url, username, password);
     PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
     
    ResultSet resultSet = preparedStatement.executeQuery();
    while (resultSet.next()) {
        int id = resultSet.getInt("id");
        String username = resultSet.getString("username");
        System.out.println("ID: " + id + ", Username: " + username);
    }
} catch (SQLException e) {
    e.printStackTrace();
}

1. Driver

  • Driver 接口
    • 负责建立与数据库的连接。
    • 各个数据库厂商提供的 JDBC 驱动程序实现了此接口。
    • 不直接使用,而是通过 DriverManager 管理和使用。

2. DriverManager

  • DriverManager 类
    • 用于管理一组 JDBC 驱动程序。
    • 负责加载 JDBC 驱动程序和建立数据库连接。
    • 常用方法:
      • getConnection(String url, String user, String password):通过指定的 URL、用户名和密码获取数据库连接。
      • getConnection(String url):通过指定的 URL 获取数据库连接。

3. Connection

  • Connection 接口
    • 表示与特定数据库的连接会话。
    • 负责创建 Statement 对象、管理事务和关闭连接。
    • 常用方法:
      • createStatement():创建一个用于执行 SQL 语句的 Statement 对象。
      • prepareStatement(String sql):创建一个用于执行预编译 SQL 语句的 PreparedStatement 对象。
      • setAutoCommit(boolean autoCommit):设置事务是否自动提交。
      • commit():提交事务。
      • rollback():回滚事务。
      • close():关闭连接。

4. Statement

  • Statement 接口
    • 用于执行静态 SQL 语句并返回结果。
    • 常用方法:
      • executeQuery(String sql):执行查询语句并返回结果集。
      • executeUpdate(String sql):执行更新语句(如 INSERTUPDATEDELETE)并返回受影响的行数。
      • execute(String sql):可以执行查询、更新或其他类型的 SQL 语句。
      • close():关闭 Statement 对象。

5. PreparedStatement

  • PreparedStatement 接口
    • 继承自 Statement 接口,用于执行预编译的 SQL 语句。
    • 通过参数化查询,可以有效防止 SQL 注入。
    • 常用方法:
      • setInt(int parameterIndex, int x):设置整数参数。
      • setString(int parameterIndex, String x):设置字符串参数。
      • setDate(int parameterIndex, Date x):设置日期参数。
      • executeQuery():执行查询语句并返回结果集。
      • executeUpdate():执行更新语句并返回受影响的行数。
      • close():关闭 PreparedStatement 对象。
      • prepareStatement对传递的参数中的敏感字符进行转义从而防止SQL注入

      • 执行存储过程的对象
        CallableStatement prepareCall (sql)

6. ResultSet

  • ResultSet 接口
    • 表示查询结果集的数据表。
    • 提供读取和操作结果集的方法。
    • 常用方法:
      • next():将光标移到下一行。
      • getInt(String columnLabel):获取指定列的整数值。
      • getString(String columnLabel):获取指定列的字符串值。
      • getDate(String columnLabel):获取指定列的日期值。
      • close():关闭 ResultSet 对象。

7. DataSource

  • DataSource 接口
    • 提供一种标准的方式来获取数据库连接。
    • 一般用于连接池实现。
    • 通过 JNDI(Java Naming and Directory Interface)来查找和获取 DataSource 对象。
    • 常用方法:
      • getConnection():获取数据库连接。

8. SQLException

  • SQLException 类
    • 表示 JDBC 操作中发生的错误或异常。
    • 提供了获取错误信息和错误码的方法。
    • 常用方法:
      • getMessage():获取错误消息。
      • getErrorCode():获取错误码。
      • getSQLState():获取 SQL 状态码。

事务管理

数据库连接池

  • 据库连接池是个容器,负责分配、管理数据库连接(Connection)
  • 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
  • 释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏

优势:

  • 资源重用
  • 提升系统响应速度
  • 避免数据库连接遗漏

Lombok

PageHelper

PageHelper 是一个用于在 MyBatis 中进行分页查询的开源分页插件。它可以帮助简化分页查询的代码,提供了一些方便的方法来进行分页处理。

pagehelper的依赖:

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>

使用思路:

表现层:

  1. 接收参数(分页参数)
  2. 调用service进行分页条件查询获取PageBean
  3. 响应

业务层:

  1. 使用PageHelper完成分页条件查询
  2. 封装PageBean对象,返回
public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO) {
        PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
        return Result.success(pageResult);
    }

在controller中用DTO接收分页参数,DTO中包含pageNum页码,pageSize每页大小这两个属性 ,PageResult包含Long total总记录数和List records当前页数据集合。

public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
        PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());
        Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);
        long total = page.getTotal();
        List<Employee> list = page.getResult();
        return new PageResult(total, list);

 在server的实现类中

  1. 使用 PageHelper.startPage 方法启动分页,pageNum 表示页码,pageSize 表示每页大小 PageHelper.startPage(pageNum, pageSize);
  2. 执行查询操作,查询结果会被封装到 PageInfo 对象中,Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);此时完整应该是List<Employee> empList=employeeMapper..Page<Employee> page=(Page<Employee>)empList;
  3. 获取 PageInfo 对象,其中包含了分页信息,List<Employee> list = page.getResult();
  4. 返回查询结果

PageInfo 对象是 MyBatis 分页插件 PageHelper 返回的分页信息对象。它包含了分页查询的相关信息,帮助开发者了解当前分页的状态、总记录数、总页数等信息。

PageInfo 类主要提供了以下一些常用的方法和属性:

  1. List<T> getList() 获取当前页的数据列表。List<T> getResult()

  2. int getPageNum() 获取当前页码。

  3. int getPageSize() 获取每页记录数。

  4. int getSize() 获取当前页的记录数。

  5. long getTotal() 获取总记录数。

  6. int getPages() 获取总页数。

基础操作

参数占位符

删除

增加(主键返回)

更新

查询

XML映射文件

在实际项目中发现不同包名的情况下也可以

使用注解来映射简单语向会码显得更加简洁,但对于稍周复杂一点的,JAVA注解不仅力不以心,还会让你本就复杂的 SQL语更加混乱不堪,一你猜如果你需要做一些很复杂的操作,最好用XML 来映射语句。

 动态SQL

<if><where><set><trim>

<trim> 用于处理 SQL 片段的前后空白字符和特定的 SQL 关键字,通常用于动态生成 SQL 语句时清理多余的关键字或符号。

<select id="selectUsers" parameterType="map" resultType="User">
    SELECT * FROM users
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
        <if test="name != null">
            AND name = #{name}
        </if>
        <if test="email != null">
            AND email = #{email}
        </if>
    </trim>
</select>
  • <trim> 标签:可以用于处理 SQL 片段的前缀和后缀,代替 <where><set> 标签,以实现动态生成 SQL 的功能。

    • prefix 属性:在结果前添加特定的 SQL 关键字(如 WHERE, SET)。
    • suffixOverrides 属性:去除结果后的多余关键字(如 ,)。
    • prefixOverrides 属性:去除结果前的多余关键字(如 AND, OR)。
  • <where> 标签:自动添加 WHERE 子句,并处理多余的前缀(AND, OR),适用于条件查询。

  • <set> 标签:自动添加 SET 子句,并去除多余的逗号,适用于更新操作。

<select><insert><update><delete>

这些标签用于定义基本的 CRUD 操作,即查询、插入、更新和删除。

<select id="selectUser" parameterType="int" resultType="User">
  SELECT * FROM users WHERE id = #{id}
</select>

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

<update id="updateUser" parameterType="User">
  UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}
</update>

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

<resultMap>

用于定义结果映射,将查询结果映射到 Java 对象中。

通过 resultType 属性,你可以直接指定结果集映射到一个 Java 类。如果 SQL 查询结果列名与 Java 类的属性名一致,MyBatis 能够自动完成基本的映射。

在这个例子中,resultType="User" 表示查询结果将自动映射到 User 类的实例。如果表列名与 User 类的属性名一致,这种映射方式就能正常工作。

<select id="selectUser" parameterType="int" resultType="User">
  SELECT id, name, email FROM users WHERE id = #{id}
</select>

使用 <resultMap> 可以提供更精细的控制,特别是在列名与属性名不一致,或者需要对复杂的对象进行映射时。

<resultMap id="visitMap" type="com.std.customer.domain.entity.StdVisit">
    <result column="visit_id" property="visitId"/>
    <result column="salesman_id" property="salesmanId"/>
    <collection property="visitMediaList" javaType="java.util.List" ofType="com.std.customer.domain.entity.StdVisitMedia">
        <result column="visit_media_id" property="visitMediaId"/>
        <result column="m_object_version_number" property="objectVersionNumber"/>
    </collection>
</resultMap>
  • <resultMap>

    • id="visitMap":为这个映射定义一个唯一的标识符,可以在查询中引用。
    • type="com.std.customer.domain.entity.StdVisit":指定这个映射的目标 Java 对象类型,即 StdVisit 类。
  • <result>

    • 用于定义数据库列到 Java 对象属性的映射。
    • column="visit_id" 对应于 property="visitId":表示 SQL 查询中的 visit_id 列会映射到 StdVisit 类的 visitId 属性。
  • <collection>

    • 用于定义一对多的关系,表示一个对象中的一个属性是一个集合。
    • property="visitMediaList":表示 StdVisit 类中的 visitMediaList 属性是一个 List 类型的集合。
    • javaType="java.util.List":指定集合的 Java 类型。
    • ofType="com.std.customer.domain.entity.StdVisitMedia":指定集合中元素的类型,即 StdVisitMedia 类。

这个 resultMap 配置适用于以下场景:

  1. 一对多关系

    • 当一个对象(如 StdVisit)具有一个集合属性(如 visitMediaList),并且这个集合需要从查询结果中填充时,可以使用 <collection> 标签进行映射。
  2. 复杂映射需求

    • 当查询结果集包含多层嵌套数据时,<resultMap> 提供了灵活的映射方式,以处理这种复杂的数据结构。

<association><collection>

<resultMap id="orderMap" type="com.example.Order">
    <id column="order_id" property="orderId"/>
    <result column="order_date" property="orderDate"/>
    //假设有一个 Order 类,其中包含一个 User 对象。
    <association property="user" javaType="com.example.User">
        <id column="user_id" property="userId"/>
        <result column="username" property="username"/>
        <result column="email" property="email"/>
    </association>
    //假设有一个 Order 类,其中包含一个 List<Product> 对象。
    <collection property="productList" ofType="com.example.Product">
        <id column="product_id" property="productId"/>
        <result column="product_name" property="productName"/>
        <result column="price" property="price"/>
    </collection>
</resultMap>
  • <association>: 映射单个对象的嵌套属性,用于处理一对一或多对一的关系。
  • <collection>: 映射集合属性,用于处理一对多或多对多的关系。

<foreach>

  • collection: 集合名称
  • item:集合遍历出来的元素/项
  • separator:每一次遍历使用的分隔符
  • open: 遍历开始前拼接的片段
  • close: 遍历结束后拼接的片段

<sql><include>

<include>用于引入其他 SQL 片段,使得 SQL 代码可以复用。

<sql>用于定义可复用的 SQL 片段,可以通过 <include> 引用。

<sql id="userColumns">
  id, name, email
</sql>

<select id="findAllUsers" resultType="User">
  SELECT <include refid="userColumns"/> FROM users
</select>

<choose>, <when>, <otherwise>

  • <choose>

    • 用于封装一组条件判断。它是一个容器标签,包含一个或多个 <when> 标签和一个可选的 <otherwise> 标签。<choose> 标签帮助你定义复杂的条件逻辑。
  • <when>

    • 用于定义条件。它通常与 <choose> 一起使用,表示当满足特定条件时的 SQL 片段。
    • test 属性指定条件表达式,符合条件的 SQL 片段会被包含在最终的 SQL 语句中。
  • <otherwise>

    • 用于定义默认的 SQL 片段。当没有 <when> 标签的条件满足时,<otherwise> 标签中的 SQL 片段将被包含在最终的 SQL 语句中。
    • 如果没有 <otherwise> 标签,且没有任何 <when> 标签的条件满足,则不会添加任何 SQL 片段。
<select id="selectUsers" parameterType="map" resultType="User">
    SELECT * FROM users
    <where>
        <choose>
            <when test="name != null">
                AND name = #{name}
            </when>
            <when test="email != null">
                AND email = #{email}
            </when>
            <otherwise>
                AND active = 1
            </otherwise>
        </choose>
    </where>
</select>
  • 如果 name 参数不为空,则 SQL 查询包含 AND name = #{name}
  • 如果 name 参数为空但 email 参数不为空,则 SQL 查询包含 AND email = #{email}
  • 如果两个参数都为空,则 SQL 查询包含 AND active = 1
<choose> 的优势
  • 互斥条件<choose><when> 更适合处理互斥条件的场景。当你希望只根据第一个满足条件的 <when> 来决定执行的 SQL 片段时,<choose> 就显得更有必要。多个 <if> 标签是并列的,它们会逐个检查并且可能都执行,而 <when> 则是互斥的,只会执行第一个满足条件的语句。

  • 默认行为:通过 <otherwise>,你可以轻松定义默认行为,即当所有条件都不满足时,系统应该如何处理。多个 <if> 标签虽然可以实现类似的逻辑,但不如 <otherwise> 直观和简洁。

<bind>

用于创建一个新的变量,并在 SQL 语句中使用。

<select id="findUserByKeyword" parameterType="String" resultType="User">
  <bind name="keyword" value="'%' + keyword + '%'" />
  SELECT * FROM users WHERE name LIKE #{keyword}
</select>

<if test="signTimeEnd != null">
    and sv.sign_time <![CDATA[ < ]]> #{signTimeEnd}
</if>

<![CDATA[ < ]]>: <![CDATA[ ... ]]> 是 XML 的一个特殊语法,用于包裹不需要被解析的内容。通常在 SQL 语句中使用比较运算符(如 <, >, <=, >=)时,由于这些符号在 XML 中具有特殊含义,所以需要使用 CDATA 标签将其包裹起来,避免被 XML 解析器误解为标签。

缓存

MyBatis 提供了缓存机制以提升数据查询的性能,减少数据库的压力。MyBatis 的缓存分为一级缓存二级缓存

1. 一级缓存

一级缓存是 MyBatis 的默认缓存机制,它的作用范围是 SqlSession。在同一个 SqlSession 中执行相同的查询时,如果之前已经执行过该查询并且结果已缓存,MyBatis 会直接从缓存中获取结果,而不会再次发起数据库查询。

  • 作用范围: 同一个 SqlSession 内。
  • 生效条件:
    • 查询操作必须是相同的(包括参数相同)。
    • SqlSession 不能被关闭。
    • 没有执行增删改操作(增删改操作会刷新一级缓存)。

一级缓存是线程不安全的,因为一个 SqlSession 通常不会在多个线程之间共享。

2. 二级缓存

二级缓存是 MyBatis 提供的全局缓存机制,作用范围是 Mapper 映射级别。不同的 SqlSession 可以共享二级缓存的数据,从而在多个 SqlSession 中复用查询结果。

  • 作用范围: Mapper 映射级别(同一个命名空间)。
  • 生效条件:
    • 必须在 MyBatis 全局配置中开启二级缓存。
    • 在映射文件中指定 <cache /> 元素来启用缓存。
    • 查询结果对象必须是可序列化的。
    • 没有执行增删改操作(增删改操作会刷新二级缓存)。

当使用不同的 SqlSession 执行相同的查询时,MyBatis 会首先检查二级缓存,如果缓存命中,则直接从缓存中读取数据;否则,会查询数据库并将结果存入缓存。

3. 二级缓存的工作机制

  • 存储结构: 二级缓存默认使用基于 PerpetualCache 实现的缓存,它是一个基础的 Map 结构。可以结合不同的缓存策略如 FIFO、LRU 等来控制缓存的行为。
  • 缓存刷新: 当执行更新、插入或删除操作时,对应命名空间的二级缓存会被清空。
  • 可配置性: MyBatis 支持自定义缓存实现,也可以使用第三方的缓存框架,如 Ehcache。

4. 一级缓存和二级缓存的区别

项目一级缓存二级缓存
作用范围SqlSession 内Mapper 映射级别(全局)
默认开启
失效条件SqlSession 关闭增删改操作
线程安全性线程不安全线程安全(可配置)
使用场景简单查询的缓存复杂查询、多线程应用

缓存的注意事项

  • 在使用二级缓存时,查询结果对象必须实现 Serializable 接口。
  • 缓存并不总是适合所有场景,对于频繁变化的数据,使用缓存可能会导致数据不一致。
  • 在集群环境中,使用分布式缓存解决方案(如 Redis、Ehcache)更为合适,以确保缓存的一致性。

通过合理使用 MyBatis 的一级缓存和二级缓存机制,可以显著提高数据库查询的性能,减少数据库的负载。但需要根据具体的应用场景权衡缓存的使用。

SqlSession

MyBatis 提供的用于执行 SQL 语句、获取映射器、管理事务的接口。SqlSession 表示与数据库的一次会话,它封装了对数据库的操作方法,如查询、插入、更新、删除等。

1. SqlSession 的职责

  • 执行 SQL 操作: SqlSession 提供了多种方法来执行 SQL 语句,例如 selectOneselectListinsertupdatedelete 等。这些方法实际上是对数据库的操作请求。

  • 获取 Mapper: SqlSession 可以获取 Mapper 接口的实例,这些实例通过动态代理的方式将接口的方法调用转换为 SQL 请求。

  • 事务管理: SqlSession 提供了对事务的管理功能,可以手动提交事务 (commit)、回滚事务 (rollback),以及关闭会话 (close)。

  • 缓存管理: SqlSession 也管理一级缓存,在同一个会话中,如果多次查询相同的数据,可能会从缓存中获取结果,而不是每次都发起数据库请求。

2. SqlSession 的生命周期

  • 创建: 通过 SqlSessionFactory 创建 SqlSession 对象。
  • 使用: 在 SqlSession 的生命周期中,可以执行多次数据库操作(多次 SQL 请求)。
  • 关闭: 当所有操作完成后,应该关闭 SqlSession,以释放资源。
SqlSessionFactory sqlSessionFactory = ...; // 从配置或编程方式创建
SqlSession sqlSession = sqlSessionFactory.openSession();

try {
    // 使用 sqlSession 执行多次数据库操作
    User user = sqlSession.selectOne("com.example.mapper.UserMapper.selectUser", 1);
    List<Order> orders = sqlSession.selectList("com.example.mapper.OrderMapper.selectOrdersByUserId", 1);

    // 提交事务(如果需要)
    sqlSession.commit();
} catch (Exception e) {
    // 回滚事务(如果发生异常)
    sqlSession.rollback();
} finally {
    // 关闭 sqlSession
    sqlSession.close();
}
  • SqlSession 表示一次会话,而不是单一的数据库请求。在一个 SqlSession 中可以执行多个数据库请求。
  • 每个 SqlSession 具有独立的缓存和事务控制,关闭 SqlSession 后,缓存也会被清除,未提交的事务会被回滚。
  • 通常在使用 SqlSession 时,会在一个方法内创建并关闭它,确保资源被及时释放。

MyBatis在代码中添加SQL的方式

使用SqlSession直接执行SQL

MyBatis提供了SqlSession对象,可以通过SqlSessionselectListupdateinsertdelete等方法执行SQL。这些SQL可以直接在Java代码中动态拼接并执行。

try (SqlSession session = sqlSessionFactory.openSession()) {
    String sql = "SELECT * FROM users WHERE id = #{id}";
    List<User> users = session.selectList("customSql", Collections.singletonMap("id", 1));
}

自定义Mapper接口中的方法

可以在Mapper接口的方法中通过@SelectProvider@InsertProvider@UpdateProvider@DeleteProvider等注解来指定一个自定义的SQL构造方法。

public interface UserMapper {
    @SelectProvider(type = UserSqlProvider.class, method = "selectByCustomCondition")
    List<User> selectByCustomCondition(Map<String, Object> params);
}

public class UserSqlProvider {
    public String selectByCustomCondition(Map<String, Object> params) {
        StringBuilder sql = new StringBuilder("SELECT * FROM users WHERE 1=1 ");
        if (params.get("name") != null) {
            sql.append("AND name = #{name} ");
        }
        if (params.get("age") != null) {
            sql.append("AND age = #{age} ");
        }
        return sql.toString();
    }
}

直接在Java代码中使用拼接

在某些复杂或者需要动态拼接SQL的场景,可以直接在Java代码中构造SQL,然后通过MyBatis执行。使用这种方法时,需要特别注意SQL注入的风险,建议对参数进行预处理或使用PreparedStatement形式。

String sql = "SELECT * FROM users WHERE name = '" + name + "' AND age = " + age;
List<User> users = sqlSession.selectList("customSql", sql);

遇到的场景,实习期间看版本号自动修改,大致思路:

//XML 中定义 SQL 模板
<!-- UserMapper.xml -->
<update id="updateUserBase" parameterType="map">
    UPDATE users
    <set>
        <if test="name != null">
            name = #{name},
        </if>
        <if test="age != null">
            age = #{age},
        </if>
        <!-- 版本号留作动态补充部分 -->
    </set>
    WHERE id = #{id}
</update>
//Java 代码中动态补充 SQL,在Java类UserSqlProvider.java中动态生成与补充版本号相关的SQL。
// UserSqlProvider.java
public class UserSqlProvider {
    public String updateVersion(Map<String, Object> params) {
        // 从参数中获取变量
        Integer newVersion = (Integer) params.get("newVersion");
        Integer oldVersion = (Integer) params.get("oldVersion");

        // 动态生成版本号更新的SQL片段
        StringBuilder sql = new StringBuilder();
        sql.append("object_version_number = ").append(newVersion);
        sql.append(" WHERE object_version_number = ").append(oldVersion);

        return sql.toString();
    }
}
//在UserMapper接口中,结合XML和Java代码来完成最终的SQL执行。
// UserMapper.java
public interface UserMapper {

    // 使用XML中的基础SQL模板
    @UpdateProvider(type = UserSqlProvider.class, method = "updateVersion")
    int updateUserBase(Map<String, Object> params);
}
  • SQL自动变更:在执行UPDATE SQL语句时,ORM框架(如MyBatis, Hibernate)会自动检查版本号是否一致,如果不一致,则更新操作会失败,从而防止数据被其他事务修改,确保数据的一致性。

  • 拦截和修改SQL

    • 在执行UPDATE操作时,ORM框架会拦截SQL语句,通过@VersionAudit注解获取版本字段的当前值。
    • ORM框架会将当前版本号与数据库中的版本号进行比较。如果版本号匹配,SQL语句会将版本号加1并更新记录。
    • 如果版本号不匹配,更新操作会失败,通常会抛出一个并发更新异常。

JPA

JPA(Java Persistence API)是 Java EE(Java Enterprise Edition)中定义的一组标准规范,用于对象持久化。JPA 允许开发者使用对象来与关系数据库进行交互,而不必编写大量的 SQL 语句。JPA 通过提供一组注解和接口,实现了对象与数据库表之间的映射关系,并简化了数据持久化的操作。

JPA 的核心概念

  1. 实体(Entity)

    • 实体类是一个轻量级的持久化领域对象,它通常与数据库中的表一一对应。每个实体类的实例代表数据库表中的一行记录。
    • 实体类通过 @Entity 注解标记。
  2. 主键(Primary Key)

    • 每个实体类都必须有一个主键,它用 @Id 注解标识。主键可以由数据库自动生成,也可以由应用程序指定。
    • 主键的生成策略可以通过 @GeneratedValue 注解进行配置,如 AUTOIDENTITYSEQUENCE 等。
  3. 关系映射(Relationships)

    • JPA 支持实体之间的关系映射,例如一对一、一对多、多对一、多对多。使用 @OneToOne@OneToMany@ManyToOne@ManyToMany 注解来定义实体之间的关系。
  4. 查询(Query)

    • JPA 提供了 JPQL(Java Persistence Query Language)来查询数据库。JPQL 类似于 SQL,但操作的是实体对象而不是表。
    • 你可以使用 @Query 注解编写 JPQL,也可以通过 EntityManager 的方法执行查询。
    • @Query("SELECT u FROM User u WHERE u.username = :username")
      User findByUsername(@Param("username") String username);
      
  5. EntityManager

    • EntityManager 是 JPA 的核心接口,用于管理实体的生命周期、执行查询、处理事务等。它提供了保存、更新、删除、查找实体的方法。

    • EntityManager em = entityManagerFactory.createEntityManager(); 
      em.getTransaction().begin(); em.persist(user); 
      em.getTransaction().commit();
  6. 事务(Transaction)

    • JPA 支持事务管理,通常与 JTA(Java Transaction API)结合使用。在 Spring 中,可以使用 @Transactional 注解来管理事务。

javax.persistence 包中的常用内容

  • 实体相关注解

    • @Entity:标记一个类为 JPA 实体。
    • @Id:标记实体类中的主键字段。
    • @GeneratedValue:用于定义主键的生成策略。
    • @Table:用于指定实体对应的数据库表名。
    • @Column:用于指定实体字段对应的数据库列名。
    • @OneToOne@OneToMany@ManyToOne@ManyToMany:用于定义实体之间的关系。
  • 查询相关注解

    • @Query:用于定义 JPQL(Java Persistence Query Language)查询。
    • @NamedQuery:用于定义命名查询。
    • @NamedQueries:用于定义多个命名查询。
  • 事务管理

    • @Transactional:标记一个方法或类支持事务管理(通常在 Spring 框架中使用,但在 JPA 中也有类似概念)。
  • 实体管理

    • EntityManager:JPA 的核心接口,用于管理实体的持久化、查询、更新和删除等操作。
    • EntityTransaction:用于处理事务。
  • 持久化单元

    • Persistence:用于创建 EntityManagerFactory 实例的类。

JPA 的优点

  • 标准化:JPA 是 Java EE 的标准,多个实现如 Hibernate、EclipseLink、OpenJPA 都遵循该规范,保证了应用程序的可移植性。
  • 面向对象:JPA 提供了将关系数据库表映射为 Java 对象的机制,使得数据库操作更加符合面向对象的编程风格。
  • 简化开发:通过注解和面向对象的查询语言(JPQL),JPA 简化了持久化层的开发,减少了冗余的 SQL 编写。
  • 事务管理:JPA 支持声明式事务管理,使得事务处理更加方便可靠。

常见的 JPA 实现:

Hibernate

  • 最流行的 JPA 实现之一,提供了丰富的功能和强大的 ORM 能力,支持多种数据库。

Spring Data JPA

  • Spring Data JPA 是 Spring 提供的 JPA 增强框架,它简化了 JPA 的使用,使得数据访问层的开发更加快捷
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;

    // getters and setters
}

public class JpaExample {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-persistence-unit");
        EntityManager em = emf.createEntityManager();

        em.getTransaction().begin();

        User user = new User();
        user.setUsername("john");
        user.setPassword("password");
        em.persist(user);

        em.getTransaction().commit();

        em.close();
        emf.close();
    }
}

Spring Data JPA

  1. 扩展和简化

    • Spring Data JPA 是 Spring 提供的一个扩展库,基于 JPA,旨在简化数据访问层的开发。
    • 提供了对 JPA 的增强功能,包括自动实现数据访问接口、简化查询、集成 Spring 的事务管理等。
  2. 核心功能

    • 提供了 JpaRepository 和其他类似的接口,自动生成常见的 CRUD 操作和查询方法。
    • 支持使用方法名称生成查询(例如 findByUsername)。
  3. 配置

    • 使用 Spring Boot 可以通过自动配置简化 JPA 配置。
    • application.propertiesapplication.yml 文件用于配置数据源和 JPA 属性。
  4. 查询

    • 支持通过方法名称生成查询,简化了查询的编写。
    • 支持 @Query 注解编写自定义 JPQL 查询。
    • 提供了动态查询的功能,如 QueryDSL 和 Specifications。
  5. 事务管理

    • 集成了 Spring 的事务管理,支持声明式事务处理(通过 @Transactional 注解)
//使用 JPA
public class UserRepository {
    private EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-persistence-unit");
    private EntityManager em = emf.createEntityManager();

    public User findByUsername(String username) {
        Query query = em.createQuery("SELECT u FROM User u WHERE u.username = :username");
        query.setParameter("username", username);
        return (User) query.getSingleResult();
    }
}
//使用 Spring Data JPA:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

对比分析

  1. 简化代码

    • JPA:需要手动实现数据访问逻辑和查询方法,代码量较多,特别是在执行复杂查询时。
    • Spring Data JPA:通过 JpaRepository 提供了自动实现的基本 CRUD 操作,并且可以通过方法名称自动生成查询,减少了大量的样板代码。
  2. 查询方法

    • JPA:使用 EntityManager 手动创建查询,编写 JPQL 或 SQL。
    • Spring Data JPA:可以通过方法名称自动生成查询(例如 findByUsername),并支持使用 @Query 注解定义自定义查询。
  3. 事务管理

    • JPA:通常依赖于 JTA 进行事务管理,需要在代码中手动管理事务。
    • Spring Data JPA:集成了 Spring 的事务管理,使用 @Transactional 注解来声明事务,简化了事务管理。
  4. 自动配置

    • JPA:需要手动配置 EntityManagerFactoryEntityManager
    • Spring Data JPA:Spring Boot 提供了自动配置,简化了 JPA 配置,减少了配置代码。
  5. 维护和扩展

    • JPA:维护和扩展时,需要编写更多的代码来处理查询和数据访问逻辑。
    • Spring Data JPA:提供了更高层次的抽象,易于维护和扩展,特别是对于常见的 CRUD 操作。

JPQL(Java Persistence Query Language)

JPQL是 JPA(Java Persistence API)定义的查询语言,用于在对象关系映射中执行数据库查询。JPQL 允许开发者使用类似于 SQL 的语法查询实体对象,而不是直接操作数据库表。它是一种面向对象的查询语言,可以操作 JPA 实体及其属性,而不是数据库表和列。

JPQL 的核心特性

  1. 面向对象

    • JPQL 查询操作的是实体对象及其属性,而不是数据库中的表和列。这使得查询更加符合面向对象的编程风格。
  2. 类似 SQL 的语法

    • JPQL 的语法与 SQL 相似,但其查询的是实体对象而非数据库表。例如,JPQL 中的 SELECT u FROM User u WHERE u.username = :username 查询的是 User 实体的对象。
  3. 动态查询

    • JPQL 支持动态查询,可以在运行时构建和执行查询。
  4. 支持关联查询

    • JPQL 支持在实体之间进行关联查询,例如 JOIN 查询来获取关联实体的数据。
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import java.util.List;

public class UserRepository {

    @PersistenceContext
    private EntityManager em;

    public List<User> findAllUsers() {
//查询所有用户:
        String jpql = "SELECT u FROM User u";
        TypedQuery<User> query = em.createQuery(jpql, User.class);
        return query.getResultList();
//使用参数查询,根据用户名查询用户:
         String jpql = "SELECT u FROM User u WHERE u.username = :username";
        TypedQuery<User> query = em.createQuery(jpql, User.class);
        query.setParameter("username", username);
//使用 JOIN 查询,查询用户及其订单:
        String jpql = "SELECT u FROM User u JOIN u.orders o";
        TypedQuery<User> query = em.createQuery(jpql, User.class);
        return query.getResultList();
//聚合函数,查询用户数量:
         String jpql = "SELECT COUNT(u) FROM User u";
        TypedQuery<Long> query = em.createQuery(jpql, Long.class);
        return query.getSingleResult();
    }
}

JPQL 语法规则

  • 选择(SELECT):用于指定要查询的实体或其属性。例如,SELECT u FROM User u 查询 User 实体的所有记录。
  • 条件(WHERE):用于指定查询的条件。例如,WHERE u.username = :username
  • 排序(ORDER BY):用于指定结果集的排序方式。例如,ORDER BY u.username ASC
  • 分页:通过设置 setFirstResultsetMaxResults 方法来进行分页查询。
  • 联接(JOIN):用于关联查询。例如,JOIN u.orders o
  • 聚合函数:如 COUNT()SUM()AVG()MAX()MIN()

Hibernate

Hibernate 和 Spring Data JPA 在用法上的差别主要体现在数据访问方式、配置复杂度、和开发效率上。以下是两者在用法上的主要差异:

1. 配置和初始化

  • Hibernate

    • 需要手动配置 hibernate.cfg.xml 或 Java-based 配置来指定数据库连接信息、实体类的映射、Hibernate 方言等。
    • 开发者需要手动管理 SessionFactorySession 对象来进行数据库操作。
  • Spring Data JPA

    • 配置更加简化,通常只需在 application.propertiesapplication.yml 文件中指定数据库连接信息,Spring Boot 自动配置大部分 JPA 相关内容。
    • 自动管理 EntityManager,通过 Spring 的依赖注入机制简化数据访问层的开发。

2. 数据访问方式

  • Hibernate

    • 主要使用 Session 对象进行数据访问。通过 Session.save()Session.update()Session.delete() 等方法进行 CRUD 操作。
    • 查询语言为 HQL(Hibernate Query Language),需要手动编写查询语句。
  • Spring Data JPA

    • 使用 JpaRepositoryCrudRepository 接口,无需手动编写基本的 CRUD 操作,大量常用操作都已封装好。
    • 支持通过方法名称约定(query methods)自动生成查询语句,例如 findByUsername(String username),Spring Data JPA 自动生成 JPQL 查询。
    • 自定义查询可以使用 @Query 注解,直接在接口方法上定义 JPQL 或原生 SQL 查询。

3. 代码示例

// Hibernate Configuration
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();

// Save a user
User user = new User();
user.setUsername("john");
user.setPassword("password");
session.save(user);

transaction.commit();
session.close();

4. 查询方式

  • Hibernate

    • 查询使用 HQL,语法类似 SQL,但操作的是实体类和属性。
    • 需要手动创建 Query 对象并执行查询。
  • Spring Data JPA

    • 支持通过方法名称推导查询,减少手动编写查询语句的工作。
    • 可以使用 @Query 注解定义复杂查询,支持 JPQL 和原生 SQL。

5. 事务管理

  • Hibernate

    • 需要手动管理事务,通常通过 Transaction 对象来控制事务的开始和提交。
  • Spring Data JPA

    • 使用 Spring 的 @Transactional 注解简化事务管理。Spring 自动处理事务的开始、提交和回滚。

6. 开发效率

  • Hibernate

    • 灵活性高,但需要编写更多的配置和样板代码。
    • 适合需要深入控制和优化的场景。
  • Spring Data JPA

    • 提供了大量的自动化功能,开发效率高。
    • 适合大多数常见的 CRUD 和查询场景,极大地减少了手动编写代码的工作量。

HQL(Hibernate Query Language)和 JPQL(Java Persistence Query Language)在很多方面非常相似,但它们也有一些关键区别。它们都是面向对象的查询语言,用于在对象关系映射(ORM)框架中编写查询语句。然而,HQL 是特定于 Hibernate 的查询语言,而 JPQL 是 JPA(Java Persistence API)的标准查询语言。

学籍管理系统是一个用于管理学生的基本信息、课程信息和成绩信息的系统。基于Vue3、Element Plus、Spring Boot、MyBatisJPA和MySQL的学籍管理系统具有以下特点和功能。 1. 前端界面使用Vue3和Element Plus框架,实现了美观、简洁的用户界面,提供了良好的用户体验。 2. 后端使用Spring Boot作为应用框架,简化了系统的搭建和开发。同时,基于MyBatisJPA技术实现与数据库的交互,提供高性能和灵活性。 3. 数据库使用MySQL,存储学生的基本信息、课程信息和成绩信息,保证了数据的持久性和安全性。 4. 学籍管理系统提供了学生信息的录入、查询和修改功能。管理员可以通过界面录入学生的基本信息,包括姓名、学号、性别、年龄等。同时,还可以查询和修改学生的信息,确保信息的准确性。 5. 系统还提供了课程信息的管理功能。管理员可以录入课程的名称、学分、教师等信息,方便学生选课和教师进行课程管理。 6. 学籍管理系统还包括成绩管理功能。管理员可以录入学生的成绩,系统会自动计算学生的平均成绩和绩点,并提供成绩查询功能,方便学生和教师查看学生成绩。 7. 系统还提供了权限管理功能,确保只有授权用户可以进行相关操作,保证了数据的安全性。 综上所述,基于Vue3、Element Plus、Spring Boot、MyBatisJPA和MySQL的学籍管理系统具有方便、快捷、安全、准确的特点,能够有效管理学生的基本信息、课程信息和成绩信息。在教育领域的学籍管理中具有重要的应用价值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值