什么是 JdbcTemplate
Spring 框架对 JDBC 进行封装,使用 JdbcTemplate
方便实现对数据库操作。
JdbcTemplate 是 Spring 框架 提供的一个核心 JDBC
工具类 ,位于 org.springframework.jdbc.core
包中。它简化了 JDBC 数据库操作 ,避免了繁琐的 JDBC
代码编写,提高了开发效率。
在Spring使用JDBC,首先我们通过IoC容器创建并管理一个DataSource
实例,然后,Spring提供了一个JdbcTemplate
,可以方便地让我们操作JDBC,因此,通常情况下,我们会实例化一个JdbcTemplate
。顾名思义,这个类主要使用了Template模式 。
作用:
执行 SQL 语句:JdbcTemplate 提供了多种方法来执行 SQL
语句,包括查询、更新、插入、删除等。
处理结果集:JdbcTemplate 可以自动将查询结果转换为 Java
对象,简化了结果集的处理。
管理数据库连接:JdbcTemplate
自动管理数据库连接的获取和释放,避免了资源泄漏。
处理异常:JdbcTemplate 将 JDBC 异常转换为 Spring
的统一数据访问异常,方便进行异常处理。
支持事务:JdbcTemplate 可以与 Spring
的事务管理机制集成,实现事务控制。
Java程序使用JDBC接口访问关系数据库的时候,需要以下几步:
创建全局DataSource
实例,表示数据库连接池;
在需要读写数据库的方法内部,按如下步骤访问数据库:
从全局DataSource
实例获取Connection
实例;
通过Connection
实例创建PreparedStatement
实例;
执行SQL语句,如果是查询,则通过ResultSet
读取结果集,如果是修改,则获得int
结果。
正确编写JDBC代码的关键是使用try ... finally
释放资源,涉及到事务的代码需要正确提交或回滚事务。
实现CURD 增删改查
我们可以将 JdbcTemplate 的创建权交给 Spring,将数据源 DataSource
的创建权也交给 Spring
在Spring容器内部将数据源 DataSource 注入到 JdbcTemplate
模版对象中,然后通过 Spring 容器获得 JdbcTemplate 对象来执行操作
准备工作
新建子模块,引入如下依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-jdbc</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > com.mysql</groupId > <artifactId > mysql-connector-j</artifactId > <scope > runtime</scope > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency >
创建 jdbc.properties,配置数据库连接池
将数据库的连接信息抽取到外部配置文件中,和spring的配置文件分离开,有利于后期维护
1 2 3 4 5 6 spring.application.name =spring6-jdbc spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver spring.datasource.url =jdbc:mysql://localhost:3306/SpringJDBCTemple?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true spring.datasource.username =spring.datasource.password =
配置 Spring 的配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xmlns:tx ="http://www.springframework.org/schema/tx" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd" > <context:component-scan base-package ="edu.software.ergoutree.spring6jdbc" /> <context:property-placeholder location ="classpath:application.properties" /> <bean id ="dataSource" class ="com.zaxxer.hikari.HikariDataSource" > <property name ="driverClassName" value ="${spring.datasource.driver-class-name}" /> <property name ="jdbcUrl" value ="${spring.datasource.url}" /> <property name ="username" value ="${spring.datasource.username}" /> <property name ="password" value ="${spring.datasource.password}" /> </bean > <bean id ="jdbcTemplate" class ="org.springframework.jdbc.core.JdbcTemplate" > <property name ="dataSource" ref ="dataSource" /> </bean > <tx:annotation-driven transaction-manager ="transactionManager" /> <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="dataSource" /> </bean > </beans >
创建需要连接的数据库
1 2 3 4 5 6 7 8 9 CREATE DATABASE IF NOT EXISTS SpringJDBCTemple; USE SpringJDBCTemple; CREATE TABLE IF NOT EXISTS t_user ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, age INT, email VARCHAR(100) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
进行测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package edu.software.ergoutree.spring6jdbc;import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import javax.sql.DataSource;import java.sql.Connection;import java.sql.SQLException;@SpringBootTest class Spring6JdbcApplicationTests { @Test public void testJdbcTemplate () throws SQLException { ApplicationContext ioc = new ClassPathXmlApplicationContext ("beans.xml" ); DataSource dataSource = ioc.getBean("dataSource" , DataSource.class); Connection connection = dataSource.getConnection(); System.out.println("dataSource's class = " + dataSource.getConnection()); System.out.println("connection = " + connection); connection.close(); } }
通过 JDBCTemplate
实现数据的添加,修改,删除
实例
向表中新增两条记录,用两种方法实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Test public void InsertJdbcTemplate () throws SQLException { ApplicationContext ioc = new ClassPathXmlApplicationContext ("beans.xml" ); DataSource dataSource = ioc.getBean("dataSource" , DataSource.class); Connection connection = dataSource.getConnection(); System.out.println("dataSource's class = " + dataSource.getConnection()); System.out.println("connection = " + connection); JdbcTemplate jdbcTemplate = ioc.getBean("jdbcTemplate" , JdbcTemplate.class); String sql1 = "INSERT INTO t_user VALUE (1113, 'zjmd', 23, '3783489@qq.com');" ; String sql2 = "INSERT INTO t_user VALUE (?, ?, ?, ?);" ; jdbcTemplate.execute(sql1); int affectedRows = jdbcTemplate.update(sql2, 221 , "wh" , 34 , "234e3434@email.com" ); System.out.println("affected rows = " + affectedRows); connection.close(); }
测试通过,发现可以插入数据
注意的是 excute()
是 JDBC
原生方法的简单封装,用于执行完整 SQL 字符串
1 2 3 4 5 6 7 int id = 111 ;String name = "zjm" ;int age = 23 ;String unsafeSql = String.format("INSERT INTO t_user VALUES (%d, '%s', %d)" , id, name, age);jdbcTemplate.execute(unsafeSql);
修改和删除的方法类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void UpdateJdbcTemplate () throws SQLException { ApplicationContext ioc = new ClassPathXmlApplicationContext ("beans.xml" ); JdbcTemplate jdbcTemplate = ioc.getBean("jdbcTemplate" , JdbcTemplate.class); String sql1 = "UPDATE t_user SET name='zjm_updated', age=25 WHERE id=1113" ; jdbcTemplate.execute(sql1); String sql2 = "UPDATE t_user SET name=?, age=? WHERE id=?" ; int affectedRows = jdbcTemplate.update(sql2, "wh_updated" , 35 , 221 ); System.out.println("Updated rows: " + affectedRows); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void DeleteJdbcTemplate () throws SQLException { ApplicationContext ioc = new ClassPathXmlApplicationContext ("beans.xml" ); JdbcTemplate jdbcTemplate = ioc.getBean("jdbcTemplate" , JdbcTemplate.class); String sql1 = "DELETE FROM t_user WHERE id=1113" ; jdbcTemplate.execute(sql1); String sql2 = "DELETE FROM t_user WHERE id=?" ; int affectedRows = jdbcTemplate.update(sql2, 221 ); System.out.println("Deleted rows: " + affectedRows); }
update
方法详解
可以看到,JDBCTemplate
对数据库的增删改操作都是update
方法,执行最终的效果只根据语句有关系
update
方法的主要作用是执行 SQL
语句并返回受影响的行数。它可以执行的 SQL 语句包括
INSERT
、UPDATE
和 DELETE
语句。
返回值:update
方法的返回值是一个整数,表示 SQL
语句执行后受影响的行数
image-20250427171122032
其中方法的重载形式:
int update(String sql)
参数说明:
使用场景 :当 SQL
语句中不包含参数时,可以使用这个方法。例如,删除表中所有数据:
1 2 3 4 5 6 7 import org.springframework.jdbc.core.JdbcTemplate;JdbcTemplate jdbcTemplate = ...;String sql = "DELETE FROM users" ;int rows = jdbcTemplate.update(sql);System.out.println("删除的行数: " + rows);
int update(String sql, @Nullable Object... args)
参数说明
sql
:要执行的 SQL 语句,其中可以包含占位符
?
。
args
:SQL
语句中占位符对应的参数值,参数的顺序要和占位符的顺序一致。
使用场景 :当 SQL
语句中包含参数时,可以使用这个方法。例如,插入一条新记录:
1 2 3 4 5 6 7 8 import org.springframework.jdbc.core.JdbcTemplate;JdbcTemplate jdbcTemplate = ...;String sql = "INSERT INTO users (name, age) VALUES (?, ?)" ;Object[] args = {"John" , 25 }; int rows = jdbcTemplate.update(sql, args);System.out.println("插入的行数: " + rows);
int update(String sql, PreparedStatementSetter pss)
参数说明
sql
:要执行的 SQL 语句,其中可以包含占位符
?
。
pss
:一个 PreparedStatementSetter
接口的实现类,用于设置 PreparedStatement
对象的参数。
使用场景 :当需要更复杂的参数设置逻辑时,可以使用这个方法。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.PreparedStatementSetter;import java.sql.PreparedStatement;import java.sql.SQLException;JdbcTemplate jdbcTemplate = ...;String sql = "UPDATE users SET age = ? WHERE name = ?" ;int age = 26 ;String name = "John" ;int rows = jdbcTemplate.update(sql, new PreparedStatementSetter () { @Override public void setValues (PreparedStatement ps) throws SQLException { ps.setInt(1 , age); ps.setString(2 , name); } }); System.out.println("更新的行数: " + rows);
异常处理
update
方法可能会抛出 DataAccessException
及其子类的异常,例如 SQLException
等。在实际使用中,建议捕获这些异常并进行相应的处理。例如:
1 2 3 4 5 6 7 8 9 10 11 12 import org.springframework.dao.DataAccessException;import org.springframework.jdbc.core.JdbcTemplate;JdbcTemplate jdbcTemplate = ...;String sql = "DELETE FROM users" ;try { int rows = jdbcTemplate.update(sql); System.out.println("删除的行数: " + rows); } catch (DataAccessException e) { System.err.println("数据库操作出错: " + e.getMessage()); }
通过 JDBCTemplate
实现数据的查询
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @Test public void SelectJdbcTemplate () throws SQLException { String sql = "SELECT * FROM t_user WHERE id=?" ; Map<String, Object> userMap = jdbcTemplate.queryForMap(sql, 1113 ); System.out.println("User as Map: " + userMap); User user = jdbcTemplate.queryForObject(sql, (rs, rowNum) -> { User u = new User (); u.setId(rs.getInt("id" )); u.setName(rs.getString("name" )); u.setAge(rs.getInt("age" )); u.setEmail(rs.getString("email" )); return u; }, 1113 ); System.out.println("User as Object: " + user); User user2 = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper <>(User.class), 1113 ); System.out.println("User as Object: " + user2); List<User> users = jdbcTemplate.query(sql, (rs, rowNum) -> { User u = new User (); u.setId(rs.getInt("id" )); u.setName(rs.getString("name" )); u.setAge(rs.getInt("age" )); u.setEmail(rs.getString("email" )); return u; }, 20 ); System.out.println("Users as Objects: " + users); List<Map<String, Object>> userMaps = jdbcTemplate.queryForList(sql, 20 ); System.out.println("Users as Maps: " + userMaps); String sql2 = "select count(*) from t_user" ; Integer count = jdbcTemplate.queryForObject(sql2, Integer.class); System.out.println("Count of users: " + count); }
queryForObject
方法详解
可以看到,JDBCTemplate
对数据库的增删改操作都是queryForObject
方法,执行的结果跟这个方法重载的各种类型有关系。
在queryForObject()
方法中,传入SQL以及SQL参数后,JdbcTemplate
会自动创建PreparedStatement
,自动执行查询并返回ResultSet
,我们提供的RowMapper
需要做的事情就是把ResultSet
的当前行映射成一个JavaBean并返回。整个过程中,使用Connection
、PreparedStatement
和ResultSet
都不需要我们手动管理。
JdbcTemplate
中的 queryForObject
方法主要用于执行 SQL 查询语句,并期望返回单个结果对象(集合).
queryForObject
方法用于执行查询操作,且预期查询结果为单个对象。当你确定查询只会返回一条记录时,就可以使用这个方法将结果映射到指定的对象类型中。
常见重载形式
T queryForObject(String sql, Class requiredType)
1 2 3 4 5 6 7 import org.springframework.jdbc.core.JdbcTemplate;JdbcTemplate jdbcTemplate = ...;String sql = "SELECT COUNT(*) FROM users" ;Integer userCount = jdbcTemplate.queryForObject(sql, Integer.class);System.out.println("用户总数: " + userCount);
T queryForObject(String sql, Object[] args, Class requiredType)
1 2 3 4 5 6 7 8 import org.springframework.jdbc.core.JdbcTemplate;JdbcTemplate jdbcTemplate = ...;String sql = "SELECT age FROM users WHERE name = ?" ;Object[] args = {"John" }; Integer age = jdbcTemplate.queryForObject(sql, args, Integer.class);System.out.println("John 的年龄: " + age);
T queryForObject(String sql, RowMapper rowMapper)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.RowMapper;import java.sql.ResultSet;import java.sql.SQLException;JdbcTemplate jdbcTemplate = ...;String sql = "SELECT * FROM users WHERE id = 1" ;User user = jdbcTemplate.queryForObject(sql, new RowMapper <User>() { @Override public User mapRow (ResultSet rs, int rowNum) throws SQLException { User user = new User (); user.setId(rs.getInt("id" )); user.setName(rs.getString("name" )); user.setAge(rs.getInt("age" )); return user; } }); System.out.println(user);
T queryForObject(String sql, Object[] args, RowMapper rowMapper)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.RowMapper;import java.sql.ResultSet;import java.sql.SQLException;JdbcTemplate jdbcTemplate = ...;String sql = "SELECT * FROM users WHERE name = ?" ;Object[] args = {"John" }; User user = jdbcTemplate.queryForObject(sql, args, new RowMapper <User>() { @Override public User mapRow (ResultSet rs, int rowNum) throws SQLException { User user = new User (); user.setId(rs.getInt("id" )); user.setName(rs.getString("name" )); user.setAge(rs.getInt("age" )); return user; } }); System.out.println(user);
返回值
queryForObject
方法的返回值是一个指定类型的对象。如果查询结果为空,该方法可能会抛出
EmptyResultDataAccessException
异常;如果查询结果有多条记录,也会抛出异常。
异常处理
在使用 queryForObject
方法时,需要注意处理可能抛出的异常,如
EmptyResultDataAccessException
和
IncorrectResultSizeDataAccessException
。
1 2 3 4 5 6 7 8 9 10 11 12 13 import org.springframework.dao.EmptyResultDataAccessException;import org.springframework.jdbc.core.JdbcTemplate;JdbcTemplate jdbcTemplate = ...;String sql = "SELECT age FROM users WHERE name = ?" ;Object[] args = {"NonExistentUser" }; try { Integer age = jdbcTemplate.queryForObject(sql, args, Integer.class); System.out.println("年龄: " + age); } catch (EmptyResultDataAccessException e) { System.err.println("未找到符合条件的记录: " + e.getMessage()); }
更进阶的使用
数据批量处理
JdbcTemplate 提供 batchUpdate()
方法实现批量操作,适合一次性处理大量数据,提升性能。
参数说明:
batchUpdate(String sql, List batchArgs)
:
batchArgs
中每个 Object[]
对应 SQL
中的一组参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void BatchInsertJdbcTemplate () { ApplicationContext ioc = new ClassPathXmlApplicationContext ("beans.xml" ); JdbcTemplate jdbcTemplate = ioc.getBean("jdbcTemplate" , JdbcTemplate.class); String sql = "INSERT INTO t_user (name, age, email) VALUES (?, ?, ?)" ; List<Object[]> batchArgs = new ArrayList <>(); batchArgs.add(new Object []{"Alice" , 28 , "alice@example.com" }); batchArgs.add(new Object []{"Bob" , 32 , "bob@example.com" }); batchArgs.add(new Object []{"Charlie" , 25 , "charlie@example.com" }); int [] affectedRows = jdbcTemplate.batchUpdate(sql, batchArgs); System.out.println("插入行数统计: " + Arrays.toString(affectedRows)); }
查询到的数据封装到 Bean 对象
通过 RowMapper
或 BeanPropertyRowMapper
将结果集自动映射到 Java 对象。
自定义 RowMapper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class UserRowMapper implements RowMapper <User> { @Override public User mapRow (ResultSet rs, int rowNum) throws SQLException { User user = new User (); user.setId(rs.getInt("id" )); user.setName(rs.getString("name" )); user.setAge(rs.getInt("age" )); user.setEmail(rs.getString("email" )); return user; } } @Test public void QueryWithCustomRowMapper () { String sql = "SELECT * FROM t_user WHERE age > ?" ; List<User> users = jdbcTemplate.query( sql, new UserRowMapper (), 20 ); System.out.println("Users: " + users); }
使用 BeanPropertyRowMapper
Spring 提供的自动映射工具,要求数据库字段名与 Java 对象属性名一致(如
user_name
→ userName
)。
1 2 3 4 5 6 7 8 9 @Test public void QueryWithBeanPropertyRowMapper () { String sql = "SELECT * FROM t_user" ; List<User> users = jdbcTemplate.query( sql, new BeanPropertyRowMapper <>(User.class) ); System.out.println("Users: " + users); }
查询到的数据以Bean对象的形式封装到集合中
直接使用 query()
方法返回
List
,适用于查询多条记录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void QueryAllUsersAsBeanList () { String sql = "SELECT * FROM t_user" ; List<User> users = jdbcTemplate.query( sql, (rs, rowNum) -> { User user = new User (); user.setId(rs.getInt("id" )); user.setName(rs.getString("name" )); user.setAge(rs.getInt("age" )); user.setEmail(rs.getString("email" )); return user; } ); System.out.println("All Users: " + users); }
查询单行单列的具体字段
使用 queryForObject()
直接返回基本类型值(如
Integer
, String
)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void QuerySingleValue () { String sqlMaxAge = "SELECT MAX(age) FROM t_user" ; Integer maxAge = jdbcTemplate.queryForObject(sqlMaxAge, Integer.class); System.out.println("最大年龄: " + maxAge); String sqlEmail = "SELECT email FROM t_user WHERE name = ?" ; String email = jdbcTemplate.queryForObject( sqlEmail, String.class, "Alice" ); System.out.println("Alice 的邮箱: " + email); }
使用具名参数添加数据
以往我们使用SQL语句时,总是用到占位符 ?
,就像我们上面刚刚用到的SQL语句
这么做有个问题——除非我们知道对应表的结构,不然就无法确定这个占位符?
应该传入什么参数 。
使用具名参数 可以很好的解决这个问题。
通过 NamedParameterJdbcTemplate
支持具名参数(如
:name
),提升 SQL 可读性。
首先在之前的 xml 配置文件下新配置一个 NamedParameterJdbcTemplate
对象,如下
1 2 3 <bean id ="namedParameterJdbcTemplate" class ="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" > <constructor-arg ref ="dataSource" /> </bean >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void InsertWithNamedParameters () { ApplicationContext ioc = new ClassPathXmlApplicationContext ("beans.xml" ); NamedParameterJdbcTemplate namedTemplate = ioc.getBean( "namedParameterJdbcTemplate" , NamedParameterJdbcTemplate.class ); String sql = "INSERT INTO t_user (name, age, email) VALUES (:name, :age, :email)" ; Map<String, Object> params = new HashMap <>(); params.put("name" , "David" ); params.put("age" , 29 ); params.put("email" , "david@example.com" ); int affectedRows = namedTemplate.update(sql, params); System.out.println("插入行数: " + affectedRows); }
使用 SqlParameterSource
更灵活的传参方式(支持 Bean 对象自动映射):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void InsertWithSqlParameterSource () { NamedParameterJdbcTemplate namedTemplate = ...; String sql = "INSERT INTO t_user (name, age, email) VALUES (:name, :age, :email)" ; User newUser = new User (); newUser.setName("Eva" ); newUser.setAge(27 ); newUser.setEmail("eva@example.com" ); SqlParameterSource params = new BeanPropertySqlParameterSource (newUser); int affectedRows = namedTemplate.update(sql, params); System.out.println("插入行数: " + affectedRows); }
总结
我们总结一下JdbcTemplate
的用法,那就是:
针对简单查询,优选query()
和queryForObject()
,因为只需提供SQL语句、参数和RowMapper
;
针对更新操作,优选update()
,因为只需提供SQL语句和参数;
任何复杂的操作,最终也可以通过execute(ConnectionCallback)
实现,因为拿到Connection
就可以做任何JDBC操作。
实际上我们使用最多的仍然是各种查询。如果在设计表结构的时候,能够和JavaBean的属性一一对应,那么直接使用BeanPropertyRowMapper
就很方便。如果表结构和JavaBean不一致怎么办?那就需要稍微改写一下查询,使结果集的结构和JavaBean保持一致。
例如,表的列名是office_address
,而JavaBean属性是workAddress
,就需要指定别名,改写查询如下
1 SELECT id, email, office_address AS workAddress, name FROM users WHERE email = ?