Java开发中,JDK自带的JDBC一直以来都因设计比较繁琐而饱受诟病(尤其是其对受检查异常的滥用和冗长的数据到对象的映射写法),对于稍大的项目,直接使用JDBC进行数据库操作开发效率不高。Spring提供了封装的JDBC模块JdbcTemplate,使用它能大大简化开发人员对数据库操作的代码。
这里我们以例子的形式进行介绍,例子的ER图如下图所示。
设计中共有学生、课程、教师三个实体类,学生和课程为多对多关系,教师和课程为一对多关系。
在传统Spring工程中使用JdbcTemplate,我们需要引入如下依赖。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.19</version>
</dependency>
在applicationContext.xml
中,我们需要配置数据源。
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<property name="poolName" value="mercatus_connection_pool"/>
<property name="dataSourceClassName"
value="com.mysql.cj.jdbc.MysqlDataSource"/>
<property name="maximumPoolSize" value="50"/>
<property name="maxLifetime" value="60000"/>
<property name="idleTimeout" value="30000"/>
<property name="dataSourceProperties">
<props>
<prop key="url">${jdbc.url}</prop>
<prop key="user">${jdbc.username}</prop>
<prop key="password">${jdbc.password}</prop>
<prop key="prepStmtCacheSize">250</prop>
<prop key="prepStmtCacheSqlLimit">2048</prop>
<prop key="cachePrepStmts">true</prop>
<prop key="useServerPrepStmts">true</prop>
</props>
</property>
</bean>
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<constructor-arg ref="hikariConfig"/>
</bean>
此外在applicationContext.xml
中还需要装配JdbcTemplate的Bean。
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>
如上配置好后,就可以在工程中使用JdbcTemplate了。
在SpringBoot中使用JdbcTemplate非常简单,首先我们需要在pom.xml
中引入相关的起步依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
此外还需要配置数据源,SpringBoot中配置数据源有多种方式,这里我们直接采用在application.properties
中进行配置。
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/stums?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&socketTimeout=30000&connectTimeout=3000&queryTimeoutKillsConnection=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.maximum-pool-size=120
spring.datasource.hikari.idle-timeout=60000
下面例子中我们编写了两个数据访问层类,实现如下几个示例功能:
StudentDao.java
package com.gacfox.demo.demojdbc.dao;
import com.gacfox.demo.demojdbc.model.Student;
import com.gacfox.demo.demojdbc.model.Teacher;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Component("studentDao")
public class StudentDao {
@Resource
private 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.demojdbc.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.demojdbc.model.Class> classList = new ArrayList<>();
List<Map<String, Object>> mapList;
mapList = jdbcTemplate.queryForList(sql, id);
for (Map<String, Object> m : mapList) {
com.gacfox.demo.demojdbc.model.Class c = new com.gacfox.demo.demojdbc.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.demojdbc.dao;
import com.gacfox.demo.demojdbc.model.Student;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Component("classDao")
public class ClassDao {
@Resource
private 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提供给我们了许多重载方法,可以用许多种不同的方式传递参数和获取结果,以上功能可以用许多不同的写法实现,我们的项目中选一种就可以了,不要写的乱七八糟。上面代码中,查询主要用了两种方法queryForMap
和queryForList
,分别用于查询单个结果和多个结果列表。
前面代码中我们将结果集映射到实体对象的操作是手动进行的,虽然灵活性更高但比较麻烦,实际上SpringJDBC也提供了自动映射的功能。注意,自动映射时实体类的属性名和数据库中的名字要相同,比如数据库字段student_id
对应属性名studentId
。
//自动映射单个结果
student = jdbcTemplate.queryForObject(sql, new Object[]{id}, new BeanPropertyRowMapper<>(Student.class));
//自动映射多个结果
studentList = jdbcTemplate.query(sql, new Object[]{id}, new BeanPropertyRowMapper<>(Student.class));
query函数其中的一个参数是RowMapper<T>
,这个参数就是用来告诉JdbcTemplate将结果集映射到哪个实体类的。
除了查询,对于其他的数据库操作:增、删、改,可以使用JdbcTemplate提供的update()
方法,对于其他的操作,比如要用代码建表,可以使用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语句。