SpringJDBC

Java开发中,JDK自带的JDBC一直以来都因设计比较繁琐而饱受诟病(尤其是其对受检查异常的滥用和冗长的数据到对象的映射写法),对于稍大型的项目,直接使用JDBC进行数据库操作开发效率不高。在很多年前,Apache DbUtils模块试图对JDBC封装一层来解决问题,再后来,Spring提供的SpringJDBC模块逐渐取代了前者的生态位,使用其中的JdbcTemplate工具类能大大简化开发人员对数据库操作的代码。

表数据准备

我们这里以例子的形式进行介绍,相关数据模型的ER图如下图所示。

设计中共有学生、课程、教师三个实体类,学生和课程为多对多关系,教师和课程为一对多关系。

工程引入和配置SpringJDBC

我们这里使用的是SpringBoot3.x版本,在SpringBoot中使用SpringJDBC需要在pom.xml中引入相关起步依赖,和大多数Spring官方的起步依赖类似,SpringJDBC依赖坐标的<version>不必手动指定,SpringBoot的父POM帮我们维护。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

除此之外,我们还需要引入数据库驱动包,我这里使用的是MySQL8数据库。

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

此外还需要配置数据源,SpringBoot中配置数据源有多种方式,这里我们直接采用在application.properties配置文件中进行配置。

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/demo?useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

手动映射查询结果集到实体对象

下面例子中我们编写了两个数据访问层类,实现如下几个示例功能。

  • 按id查询学生
  • 查询所有学生
  • 查询某个学生选的所有课程
  • 查询某个课程的所有学生
  • 查询某个学生的所有老师

StudentDao.java

package com.gacfox.demo.dao;

import com.gacfox.demo.model.Student;
import com.gacfox.demo.model.Teacher;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Component("studentDao")
public class StudentDao {
    private final JdbcTemplate jdbcTemplate;

    public StudentDao(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    /**
     * 根据学生ID查询学生信息
     *
     * @param id 学生ID
     * @return 学生对象
     */
    public Student queryStudentById(Long id) {
        String sql = "select * from t_student where student_id = ?";

        Map<String, Object> resultMap = jdbcTemplate.queryForMap(sql, id);
        Student student = new Student();
        student.setStudentId((Long) resultMap.get("student_id"));
        student.setStudentName((String) resultMap.get("student_name"));
        return student;
    }

    /**
     * 查询所有学生
     *
     * @return 包含所有学生对象的列表
     */
    public List<Student> queryAllStudents() {
        String sql = "select * from t_student order by student_id asc";
        List<Student> studentList = new ArrayList<>();
        List<Map<String, Object>> mapList;
        mapList = jdbcTemplate.queryForList(sql);
        for (Map<String, Object> m : mapList) {
            Student student = new Student();
            student.setStudentId((Long) m.get("student_id"));
            student.setStudentName((String) m.get("student_name"));
            studentList.add(student);
        }
        return studentList;
    }

    /**
     * 查询某个学生选的所有课
     *
     * @param id 学生ID
     * @return 包含课程对象的列表
     */
    public List<com.gacfox.demo.model.Class> queryAllClassesByStudentId(Long id) {
        String sql = "select distinct t_class.class_id,t_class.class_name,t_class.teacher_id " +
                "from t_class " +
                "inner join t_student_class on t_class.class_id=t_student_class.class_id " +
                "where t_student_class.student_id = ?";
        List<com.gacfox.demo.model.Class> classList = new ArrayList<>();
        List<Map<String, Object>> mapList;
        mapList = jdbcTemplate.queryForList(sql, id);
        for (Map<String, Object> m : mapList) {
            com.gacfox.demo.model.Class c = new com.gacfox.demo.model.Class();
            c.setClassId((Long) m.get("class_id"));
            c.setClassName((String) m.get("class_name"));
            classList.add(c);
        }
        return classList;
    }

    /**
     * 查询某个学生的所有教师
     *
     * @param id 学生ID
     * @return 包含教师对象的列表
     */
    public List<Teacher> queryTeachersByStudentId(Long id) {
        String sql = "select distinct t_teacher.teacher_id,t_teacher.teacher_name from t_teacher " +
                "inner join t_class on t_teacher.teacher_id = t_class.teacher_id " +
                "inner join t_student_class on t_class.class_id = t_student_class.class_id " +
                "where t_student_class.student_id = ?";
        List<Teacher> teacherList = new ArrayList<>();
        List<Map<String, Object>> mapList;
        mapList = jdbcTemplate.queryForList(sql, id);
        for (Map<String, Object> m : mapList) {
            Teacher teacher = new Teacher();
            teacher.setTeacherId((Long) m.get("teacher_id"));
            teacher.setTeacherName((String) m.get("teacher_name"));
            teacherList.add(teacher);
        }
        return teacherList;
    }
}

ClassDao.java

package com.gacfox.demo.dao;

import com.gacfox.demo.model.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Component("classDao")
public class ClassDao {
    private final JdbcTemplate jdbcTemplate;

    @Autowired
    public ClassDao(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    /**
     * 查询某门课程的所有学生
     *
     * @param id 课程ID
     * @return 包含学生对象的列表
     */
    public List<Student> queryStudentsByClassId(Long id) {
        String sql = "select t_student.student_id,t_student.student_name from t_student " +
                "inner join t_student_class on t_student.student_id = t_student_class.student_id " +
                "where t_student_class.class_id=?";
        List<Student> studentList = new ArrayList<>();
        List<Map<String, Object>> mapList;
        mapList = jdbcTemplate.queryForList(sql, id);
        for (Map<String, Object> m : mapList) {
            Student student = new Student();
            student.setStudentId((Long) m.get("student_id"));
            student.setStudentName((String) m.get("student_name"));
            studentList.add(student);
        }
        return studentList;
    }
}

为了方便演示,代码中未对具有关联关系的实体进行关联查询赋值,实际情况下应根据具体需求实现。从代码中我们可以看到,JdbcTemplate提供给我们了许多重载方法,可以用许多种不同的方式传递参数和获取结果,以上功能可以用许多不同的写法实现,我们的项目中选一种就可以了,不要写的乱七八糟。上面代码中,查询主要用了两种方法queryForMapqueryForList,分别用于查询单个结果和多个结果列表。

自动映射查询结果集到实体对象

前面代码中我们将结果集映射到实体对象的操作是手动进行的,虽然灵活性更高但比较麻烦,实际上SpringJDBC也提供了自动映射的功能。注意,自动映射时实体类的属性名和数据库中的名字要相同,比如数据库字段student_id对应属性名studentId

//自动映射单个结果
Student student = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Student.class), id);
//自动映射多个结果
List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class), id);

query函数的第2个参数是RowMapper<T>,这个参数就是用来告诉JdbcTemplate将结果集映射到哪个实体类的。

使用execute()和update()

除了查询,对于DML操作包括增、删、改都可以使用JdbcTemplate提供的update()方法实现。此外,对于其他DDL语句可以使用execute()方法执行。

public int update(String sql,
         Object... args)
           throws org.springframework.dao.DataAccessException

update()中,参数是SQL语句和SQL参数,返回值是受影响的列数。

public void execute(String sql)
             throws org.springframework.dao.DataAccessException

execute()中,参数是SQL语句。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。