Spring JDBC的使用详解
JDBC介绍
从这篇文章开始,我们将会介绍SpringBoot另外一个核心的技术,即数据库访问技术,提到数据访问,学习Java的同学瞬间能就想起JDBC技术,JDBC 是 Java Database Connectivity 的全称,是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的一套标准的API,这套标准不同的数据库厂家之间共同准守,并提供各自的具体实现。如图所示:
这样设计的好处,就是Java程序只需要和JDBC API交互,从而屏蔽了访问数据库的复杂的实现,大大降低了Java程序访问数据库的复杂度。对于日常开发而言,我们只需要掌握JDBC API 规范中的几个核心编程对象即可,这些对象包括DriverManger、Connection、Statement及ResultSet。
DriverManager
DriverManager主要负责加载不同数据库厂家提供的驱动程序包(Driver),并且根据不同的请求向Java程序返回数据库连接(Connection)对象,先看下Driver接口的定义:
public interface Driver { //获取数据库连接 Connection connect(String url, java.util.Properties info) throws SQLException; boolean acceptsURL(String url) throws SQLException; DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) throws SQLException; int getMajorVersion(); int getMinorVersion(); boolean jdbcCompliant(); public Logger getParentLogger() throws SQLFeatureNotSupportedException; }
Driver中有个重要的方法 connect,来提供Connection对象
不同的数据库对Driver,有具体的实现,以MySql为例:
public class Driver extends NonRegisteringDriver implements java.sql.Driver { // 通过 DriverManager 注册 Driver static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } … }
这里用到了DriverManager,DriverManager通过 registerDriver来注册不同数据库的Driver,并且还提供了getConnection返回数据库连接对象。
Connection
通过DriverManager可以获取Connetion对象,Connection对象可以理解与数据库连接的一种会话(Session),一个Connection对象代表一个数据库的连接,负责完成与数据库底层的通讯。
Connection对象提供了一组重载的方法来创建Statement和PreparedStatement,Statement和PreparedStatement是SQL执行的载体,另外Connection对象还会涉及事务相关的操作。
Connection对象最核心的几个方法如下:
public interface Connection extends Wrapper, AutoCloseable { //创建 Statement Statement createStatement() throws SQLException; //创建 PreparedStatement PreparedStatement prepareStatement(String sql) throws SQLException; //提交 void commit() throws SQLException; //回滚 void rollback() throws SQLException; //关闭连接 void close() throws SQLException; }
Statement/PreparedStatement
Statement和PreparedStatement是由Connection对象来创建的,用来执行静态的SQL语句并且返回生成的结果集对象,这里存在两种类型,一种是普通的Statement,另外一种支持预编译的PreparedStatement。
所谓预编译,是指数据库的编译器会对 SQL 语句提前编译,然后将预编译的结果缓存到数据库中,下次执行时就可以通过替换参数并直接使用编译过的语句,从而大大提高 SQL 的执行效率。
以Statement为例,看下Statement最核心的方法:
public interface Statement extends Wrapper, AutoCloseable { //执行查询语句 ResultSet executeQuery(String sql) throws SQLException; //执行更新语句 int executeUpdate(String sql) throws SQLException; //执行 SQL 语句 boolean execute(String sql) throws SQLException; //执行批处理 int[] executeBatch() throws SQLException; }
ResultSet
通过Statement或PreparedStatement执行SQL语句,我们引出了另外一个对象即为ResultSet对象,代码如下:
public interface ResultSet extends Wrapper, AutoCloseable { //获取下一个结果 boolean next() throws SQLException; //获取某一个类型的结果值 Value getXXX(int columnIndex) throws SQLException; … }
ResultSet对象提供了next()方法,用来对整个结果集遍历操作,如果next()方法返回为true,说明还有下一条记录,
我们可以调用 ResultSet 对象的一系列 getXXX() 方法来取得对应的结果值。
JDBC访问数据库流程
对于开发人员而言,通过JDBC的API是Java访问数据库的主要途径,下面用代码来展示下访问数据库的一个整体流程:
String url = "jdbc:mysql://localhost:3306/test" ; String username = "root" ; String password = "root" ; //1.通过DriverManager获取connection连接 Connection connection = DriverManager.getConnection(url,username,password); //2.创建preparedStatement PreparedStatement preparedStatement = connection.prepareStatement("select * from user"); //3.执行SQL返回ResultSet ResultSet resultSet = preparedStatement.executeQuery(); //4.遍历resultSet结果集 while (resultSet.next()){ //resultSet.getString("1"); } //5.释放资源 resultSet.close(); preparedStatement.close(); connection.close();
配置数据源
上面我们在介绍JDBC的时候,Connection对象是通过DriverManager获取,Connection对象代表着和数据库的连接,每次通过DriverManager获取比较耗时,影响了系统的性能。那有没有办法能够复用Connection对象呢,答案是肯定的
JDBC给我们提供了DataSource接口来实现Connection的复用,核心代码如下:
public interface DataSource extends CommonDataSource, Wrapper { Connection getConnection() throws SQLException; Connection getConnection(String username, String password) throws SQLException; }
作为一种基础组件,不需要开发人员自己实现 DataSource,因为业界已经存在了很多优秀的实现方案,如 DBCP、C3P0 、Druid 、Hikari等
SpringBoot默认HikariDataSource作为DataSource的实现,现在我们SpringBoot为例,看下SpringBoot如何通过JDBC来操作数据库的,在进行数据库操作之前,我们首先需要先配置DataSource,SpringBoot配置DataSource非常简单,只需要在配置文件中添加DataSource的配置:
spring: # datasource 数据源配置内容 datasource: url: jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8 driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root
使用JDBC操纵数据库
DataSource配好后,我们在本地的数据库服务中,创建一个test数据库,并且执行以下DDL创建user表
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `username` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户名', `password` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '密码', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
接下来我们创建一个实体类,实体类中属性和user表中字段一一对应
@Data public class User { /** * 主键 */ private Integer id; /** * 用户名 */ private String username; /** * 密码 */ private String password; /** * 创建时间 */ private Date createTime; }
注意:这里使用了Lombok的@Data注解来生成get/set方法。
我们再定义一个UserDao接口,
public interface UserDao { /** * 新增 * @param user * @return */ Integer insert(User user); /** * 根据ID查询 * @param id * @return */ User selectById(Integer id); /** * 根据ID更新 * @param user * @return */ Integer updateById(User user); /** * 根据ID删除 * @param id * @return */ Integer deleteById(Integer id); }
这里之所以要抽离出一个UserDao一层有两个原因:第一UserDao只封装了对use表的数据库操作,代码易于维护和管理,第二我们可以基于UserDao接口提供不同的实现来访问数据库,比如我们可以提供基于原生JDBC的实现,也可以用JDBCTemplate实现数据库的访问,还可以通过Mybatis等
接下来将通过代码形式来展示下SpringBoot是如何通过JDBC API对数据库进行CRUD操作的。我们来定义UserDao的具体实现类命名为:UserRawJdbcDao实现以下方法:
新增数据
@Override public Integer insert(User user) { final String SQL_INSERT = "INSERT INTO user(username, password, create_time) VALUES(?, ?, ?)"; Connection connection = null; PreparedStatement statement = null; ResultSet rs = null; Integer count = 0; try{ connection = dataSource.getConnection(); statement = connection.prepareStatement(SQL_INSERT, Statement.RETURN_GENERATED_KEYS); statement.setString(1,user.getUsername()); statement.setString(2,user.getPassword()); statement.setTimestamp(3,new Timestamp(user.getCreateTime().getTime())); count = statement.executeUpdate(); rs = statement.getGeneratedKeys(); if(rs.next()){ user.setId(rs.getInt(1)); } }catch (SQLException e){ e.printStackTrace(); }finally { try { if(rs != null){ rs.close(); } if(statement != null){ statement.close(); } if(connection != null){ connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } return count; }
查询数据
@Override public User selectById(Integer id) { final String SQL_SELECT_ID = "SELECT id,username,password,create_time FROM user WHERE id = ?"; Connection connection = null; PreparedStatement statement = null; ResultSet rs = null; User user = null; try{ connection = dataSource.getConnection(); statement = connection.prepareStatement(SQL_SELECT_ID); statement.setInt(1, id); rs = statement.executeQuery(); if(rs.next()){ user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setPassword(rs.getString("password")); user.setCreateTime(rs.getTimestamp("create_time")); } }catch (SQLException e){ e.printStackTrace(); }finally { try { if(rs != null){ rs.close(); } if(statement != null){ statement.close(); } if(connection != null){ connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } return user; }
更新数据
@Override public Integer updateById(User user) { final String SQL_UPDATE = "UPDATE user SET username = ?, password = ? WHERE id = ?"; Connection connection = null; PreparedStatement statement = null; ResultSet rs = null; Integer count = 0; try{ connection = dataSource.getConnection(); statement = connection.prepareStatement(SQL_UPDATE); statement.setString(1,user.getUsername()); statement.setString(2,user.getPassword()); statement.setInt(3,user.getId()); count = statement.executeUpdate(); }catch (SQLException e){ e.printStackTrace(); }finally { try { if(rs != null){ rs.close(); } if(statement != null){ statement.close(); } if(connection != null){ connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } return count; }
删除数据
@Override public Integer deleteById(Integer id) { final String SQL_DELETE = "DELETE FROM user WHERE id = ?"; Connection connection = null; PreparedStatement statement = null; ResultSet rs = null; Integer count = 0; try{ connection = dataSource.getConnection(); statement = connection.prepareStatement(SQL_DELETE); statement.setInt(1,id); count = statement.executeUpdate(); }catch (SQLException e){ e.printStackTrace(); }finally { try { if(rs != null){ rs.close(); } if(statement != null){ statement.close(); } if(connection != null){ connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } return count; }
到此,SpringBoot通过调用原生的JDBC的API完成对user表的CRUD操作,这代码对有代码洁癖的同学简直不能忍,有大量共性的代码,如创建Connection、Statement、ResultSet、资源的释放和异常的处理。这部分封装和优化SpringBoot已经处理过了,SpringBoot提供了JdbcTemplate模板工具类实现数据访问,它简化了JDBC API的使用方法。
使用JdbcTemplate操纵数据库
同UserRawJdbcDao,我们再定义UserDao的另外一套实现类命名为:UserJdbcDao,这套实现类是通过JdbcTemplate完成对数据库的操作,完成接口定义的方法如下:
新增数据
@Override public Integer insert(User user){ // 创建 KeyHolder 对象,设置返回的主键 ID KeyHolder keyHolder = new GeneratedKeyHolder(); int count = jdbcTemplate.update(INSERT_PREPARED_STATEMENT_CREATOR_FACTORY.newPreparedStatementCreator( Arrays.asList(user.getUsername(),user.getPassword(),user.getCreateTime())),keyHolder); // 设置 ID 主键到 entity 实体中 if (keyHolder.getKey() != null) { user.setId(keyHolder.getKey().intValue()); } // 返回影响行数 return count; }
查询数据
@Override public User selectById(Integer id){ User result = jdbcTemplate.queryForObject("SELECT id, username, password, create_time FROM user WHERE id=?", new BeanPropertyRowMapper<>(User.class), id); return result; }
更新数据
@Override public Integer updateById(User user) { return jdbcTemplate.update("UPDATE user SET username = ?, password = ? WHERE id = ?", user.getUsername(),user.getPassword(),user.getId()); }
删除数据
@Override public Integer deleteById(Integer id){ return jdbcTemplate.update("DELETE FROM user WHERE id = ?", id); }
小结
通过对比我们发现使用JdbcTemplate模板工具类可以大大减少JDBC访问数据库的代码复杂度,作为开发人员我们应该只关心业务逻辑的具体实现过程,对JDBC底层对象的创建,资源的释放,异常的捕获,应该交给框架统一维护和管理。
虽然JdbcTemplate减少的我们访问数据库的代码量,不过使用也有一些问题,比如:新增数据的时候默认无法返回生成主键的id,将SQL硬编码到Java代码中,如果SQL修改,需要重新编译Java代码,不利于系统的维护等。这时我们需要另外一个框架,它就是大名鼎鼎的Mybatis,下一篇我将会介绍SpringBoot如何整合Mybatis。
项目源码
github:github.com/dragon8844/…
以上就是Spring JDBC的使用详解的详细内容,更多关于Spring JDBC的使用的资料请关注猪先飞其它相关文章!
相关文章
- 这篇文章主要介绍了Spring AOP 对象内部方法间的嵌套调用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-08-29
Spring Cloud 中@FeignClient注解中的contextId属性详解
这篇文章主要介绍了Spring Cloud 中@FeignClient注解中的contextId属性详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-25Springboot如何实现Web系统License授权认证
这篇文章主要介绍了Springboot如何实现Web系统License授权认证,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-05-28- 这篇文章主要介绍了详解SpringCloudGateway内存泄漏问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-07-16
如何在Spring WebFlux的任何地方获取Request对象
这篇文章主要介绍了如何在Spring WebFlux的任何地方获取Request对象,帮助大家更好的理解和使用springboot框架,感兴趣的朋友可以了解下...2021-01-26- @Autowired 注解的主要功能就是完成自动注入,使用也非常简单,但这篇文章主要给大家介绍了关于Spring为什么不推荐使用@Autowired注解的相关资料,需要的朋友可以参考下...2021-11-03
Springboot如何使用mybatis实现拦截SQL分页
这篇文章主要介绍了Springboot使用mybatis实现拦截SQL分页,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-06-19- 这篇文章主要介绍了SpringMVC文件上传原理及实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-07-15
Spring Data JPA 关键字Exists的用法说明
这篇文章主要介绍了Spring Data JPA 关键字Exists的用法说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-06-10ShardingSphere jdbc集成多数据源的实现步骤
本文主要介绍了ShardingSphere jdbc集成多数据源的实现步骤,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-10-21tomcat启动完成执行 某个方法 定时任务(Spring)操作
这篇文章主要介绍了tomcat启动完成执行 某个方法 定时任务(Spring)操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-25使用Maven 搭建 Spring MVC 本地部署Tomcat的详细教程
这篇文章主要介绍了使用Maven 搭建 Spring MVC 本地部署Tomcat,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-08-16- 这篇文章主要介绍了Spring Cloud负载均衡及远程调用实现详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2021-09-18
- 这篇文章主要介绍了SpringMvc自动装箱及GET请求参数原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-09-19
- 这篇文章主要介绍了SpringMvc获取请求头请求体消息过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-09-17
Java连接MySQL8.0 JDBC的详细步骤(IDEA版本)
这篇文章主要介绍了Java连接MySQL8.0 JDBC的详细步骤(IDEA版本),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-06- 这篇文章主要介绍了Springboot使用thymeleaf动态模板实现刷新,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-08-31
Idea打包springboot项目没有.original文件解决方案
这篇文章主要介绍了Idea打包springboot项目没有.original文件解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-07-26- 这篇文章主要介绍了spring boot 使用utf8mb4的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-20
Springmvc ResponseBody响应json数据实现过程
这篇文章主要介绍了Springmvc ResponseBody响应json数据实现过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-10-26