当前位置:Java -> 使用 Spring JdbcTemplate 与 JdbcTemplateMapper

使用 Spring JdbcTemplate 与 JdbcTemplateMapper

JdbcTemplate是Spring使用JDBC从关系数据库中提供数据访问的选项。对于某些应用程序来说,使用ORM及其细微差别/复杂性可能不太合适,此时JdbcTemplate就是个选择。

JdbcTemplate抽象出了大量JDBC底层代码,但仍然十分冗长。当选择JdbcTemplate时,通常开发人员会实现一个抽象层,以减少冗长性。

JdbcTemplateMapper库是围绕JdbcTemplate的一个包装器,其目标是减少JdbcTemplate的使用冗长性。在模型上撒上几个注解,你就可以获得单行CRUD和流畅式的查询,例如hasOne, hasMany, hasMany through(多对多)等,以及其他一些开发人员友好的特性。在适当的情况下使用它,并继续使用JdbcTemplate进行其他功能,如存储过程、自定义查询、批处理等。

CRUD示例

@Table(name = "employee")
public class Employee {
  @Id(type = IdType.AUTO_INCREMENT)
  private Integer id; // maps to id column in table. The id gets assigned on insert.
  
  @Column
  private String lastName; // maps to last_name column by default 
  
  @Column
  private String firstName; // maps to first_name column by default

  @Column
  private LocalDateTime startDate; // maps to start_date column by default

  @Column
  private Integer departmentId; // maps to department_id. Foreign key

  private Department department; // there are no mappings for relationships
  ...
}

@Table(name = "department")
public class Department {
  @Id(type = IdType.AUTO_INCREMENT)
  private Integer id; // maps to id column in table. The id gets assigned on insert

  @Column(name = "department_name")
  private String name; // maps to a non default column department_name in table
  
  private List<Employee> employees = new ArrayList<>(); // there are no mappings for relationships
  ...
}

// Usage:
  ...
  @Autowired
  private JdbcTemplateMapper jtm; 
  ...
  Department dept = new Department();
  dept.setName("HR department");
  jtm.insert(dept); // auto assigns id on insert since id configured as auto increment
  
  Employee emp = new Employee();
  emp.setFirstName("John");
  emp.setLastName("Doe");
  emp.setStartDate(LocalDateTime.now());
  emp.setDepartmentId(dept.getId());
  jtm.insert(emp); // auto assigns id on insert since id configured as auto increment
  
  emp = jtm.findById(Employee.class, emp.getId());
  emp.setLastName("Smith");
  jtm.update(emp);
    
...                                


查询关系

JdbcTemplate的冗长性体现在需要从多个表中查询信息时。比如,你想在两个表之间进行联接以填充关系。在这种情况下,你不能使用SELECT *,因为涉及多个表,所以你必须在SELECT语句中输入列名。在大多数情况下,仅此是不够的,因为来自多个表的列名可能存在冲突; 例如,idaudit字段等。这需要为列添加别名,以防止JDBC中出现冲突。在处理真实世界的应用程序时,表往往有相当多的列,这样查询变得非常冗长。请注意,这仅适用于SELECT语句。您还需要实现自定义的RowMapper,将值从ResultSet映射到对象,其中您必须逐个映射每个ResultSet列到对象。

与使用JdbcTemplateMapperQuery功能时下面的代码进行比较。它负责生成SQL语句。在幕后,它使用JdbcTemplate.query()来检索记录。

// query the employee hasOne department relationship
List<Employee> employees = Query.type(Employee.class) // owning class
                                .hasOne(Department.class) // related class
                                .joinColumnOwningSide("department_id") // join column (the foreign key) is on owning (employee) table
                                .populateProperty("department")
                                .execute(jtm);

// query the department hasMany employee relationship
List<Department> departments = 
        Query.type(Department.class)
             .hasMany(Employee.class)
             .joinColumnManySide("department_id") // join column (the foreign key) is on many side table employee
             .populateProperty("employees")
             .where("department.department_name like ?", "HR%")
             .orderBy("employee.last_name")
             .execute(jtm);


其他查询特性

Query通过limitOffsetClause()支持分页。要获得分页的总计数,可以使用QueryCount

QueryMerge将一个查询的结果与另一个查询的结果合并。这在需要查询多个关系时非常方便。

以下是使用QueryQueryMergeQueryCount进行分页的简单示例。有关更详细的示例,请参阅JdbcTemplateMapper文档。

// Paginated query for departments
List<Department> departments = 
        Query.type(Department.class)
             .where("department_name like ?", "HR%")
             .orderBy("department_name")
             .limitOffsetClause("LIMIT 10 OFFSET 0")  // MySQL syntax. Would be different for other databases.
             .execute(jtm);
      
// QueryMerge will issue an SQL 'IN' clause with department ids and populate the employees
// for the corresponding departments
QueryMerge.type(Department.class)
          .hasMany(Employee.class)
          .joinColumnManySide("department_id") // join column (the foreign key) is on many side table employee
          .populateProperty("employees")
          .execute(jtm, departments); // merges employees to their corresponding department  
      
// To get total count of records
Integer count = QueryCount.type(Department.class)
                          .where("department_name like ?", "HR%")
                          .execute(jtm);
      


更新的乐观锁定

对于一些应用程序,乐观锁定至关重要,这样就不会让更新发生冲突。该库通过@Version注解提供了对乐观锁定的支持。在更新过期数据时,将抛出OptimisticLocking异常。

jdbctemplatemapper的pom.xml条目

<dependency>
   <groupId>io.github.jdbctemplatemapper</groupId>
   <artifactId>jdbctemplatemapper</artifactId>
   <version>2.3.1</version> <!-- Check and use lastest from jdbctemplatemapper site -->
</dependency>


Spring Bean配置

  @Bean
  public JdbcTemplateMapper jdbcTemplateMapper(JdbcTemplate jdbcTemplate) {
    return  new JdbcTemplateMapper(jdbcTemplate);
  }


记录SQL日志

application.properties文件中进行以下项配置,记录SQL。

# log the sql
logging.level.org.springframework.jdbc.core.JdbcTemplate=TRACE

# log the INSERT statements
logging.level.org.springframework.jdbc.core.simple.SimpleJdbcInsert=TRACE

# log the parameters of sql statement
logging.level.org.springframework.jdbc.core.StatementCreatorUtils=TRACE


该库提供了像@CreatedBy@UpdatedBy@CreatedOn@UpdatedOn这样的记录审核注解,如果模型被这些注解标记,则会自动赋值。它支持Java8、Java11和Java17(所有LTSJava版本)以及相应的Spring Boot版本。例如,它适用于使用Java17的Spring Boot 3.x版本。该库的测试针对PostgreSQL、MySQL、Oracle和SQLServer运行。根据数据库/驱动程序的版本,它应该可以与其他关系数据库一起使用。

该库的唯一依赖是spring-boot-starter-jdbc(这是Spring JdbcTemplate所需的依赖项)。

这个教程的源代码在GitHub上可以找到。该教程针对MySQL数据库,并且源代码中提供了JdbcTemplate的配置。还提供了运行代码的指导。

推荐阅读: FastApi、Flask、Django如何选择

本文链接: 使用 Spring JdbcTemplate 与 JdbcTemplateMapper