背景
小白刚入公司时,领导指定小白的师傅为扫地僧。在介绍公司内部框架时,小白提出了自己的疑问:
小白:师傅,数据库操作,JDBC用着挺好的,为什么非要使用mybatis,hibernate,Spring jdbcTemplate这些框架,这样会不会导致新手入门门槛变高?
扫地僧:如果你想要变成一个整天忙的脚不沾地,四处救火的程序员,那你可以选择直接使用JDBC,如果你只想专注于业务开发,那么像Mybatis这些框架,可以帮我们把脏活累活帮我们处理好.事实胜于雄辩。下面,我来演示一个使用jdbc访问mysql数据库的示例,你来看看如果变成线上的项目,有什么地方值得改进?
Jdbc不香吗?
准备工作
mysql数据库,本实例的版本为:8.0.16
mysql客户端SQLyog(免费,不需要注册码)或者navicat for mysql
创建数据库www和表
CREATEdatabase davidwang456;use davidwang456;DROPTABLEIFEXISTSstudent;CREATETABLE`student`(`id`int(11)NOTNULLAUTO_INCREMENT,`first_name`varchar(100)DEFAULTNULL,`last_name`varchar(100)DEFAULTNULL,`age`int(11)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;mock数据
INSERTINTO`student`VALUES(1,'wang1','david1',25);INSERTINTO`student`VALUES(2,'wang2','david2',25);INSERTINTO`student`VALUES(3,'wang3','david3',25);INSERTINTO`student`VALUES(4,'wang4','david4',25);INSERTINTO`student`VALUES(5,'wang5','david5',25);INSERTINTO`student`VALUES(6,'wang6','david6',25);INSERTINTO`student`VALUES(7,'wang7','david7',25);INSERTINTO`student`VALUES(8,'wang8','david8',25);创建maven项目
添加依赖
完整依赖文件pom.xml(包含了后面用到的spring jdbcTemplate依赖相关)
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.davidwang45.mybatis</groupId> <artifactId>JdbcTest</artifactId> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <version>1.2.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.16</version> <scope>provided</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.9.RELEASE</version> </dependency> </dependencies> </project>测试程序
使用JDBC操作数据库的基本步骤大致相同:
加载(注册)数据库驱动(到JVM)。
建立(获取)数据库连接。
定义操作的SQL语句,实例化PreparedStatement对象,设置入参。
执行数据库操作。
获取并操作结果集。
关闭对象,回收数据库资源(关闭结果集–>关闭数据库操作对象–>关闭连接)
importjava.sql.Connection;importjava.sql.DriverManager;importjava.sql.PreparedStatement;importjava.sql.ResultSet;importjava.sql.SQLException;importjava.sql.Statement;publicclassJdbcTest{// MySQL 8.0 以下版本 - JDBC 驱动名及数据库 URL//static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";// static final String DB_URL = "jdbc:mysql://localhost:3306/davidwang456";// MySQL 8.0 以上版本 - JDBC 驱动名及数据库 URLstaticfinalStringJDBC_DRIVER="com.mysql.cj.jdbc.Driver";staticfinalStringDB_URL="jdbc:mysql://localhost:3306/davidwang456?characterEncoding=UTF-8&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC";// 数据库的用户名与密码,需要根据自己的设置staticfinalStringUSER="root";staticfinalStringPASS="wangwei456";publicstaticvoidmain(String[]args){Connectionconn=null;PreparedStatementpreparedStatement=null;ResultSetrs=null;Stringsql="";try{//1 注册 JDBC 驱动Class.forName(JDBC_DRIVER);//2 打开链接System.out.println("连接数据库...");conn=DriverManager.getConnection(DB_URL,USER,PASS);//3 定义操作的SQL语句,实例化PreparedStatement对象,设置入参sql="SELECT id, first_name, last_name,age FROM student where id = ?";System.out.println(" 实例化PreparedStatement对象...");preparedStatement=conn.prepareStatement(sql);preparedStatement.setInt(1,5);//4 执行数据库操作rs=preparedStatement.executeQuery();//5 获取并操作结果集while(rs.next()){// 通过字段检索intid=rs.getInt("id");Stringfirst_name=rs.getString("first_name");Stringlast_name=rs.getString("last_name");intage=rs.getInt("age");//输出数据System.out.println("[ID: "+id+",first_name:"+first_name+",last_name:"+last_name+",age:"+age+"]");}//6 完成后关闭// 关闭资源shutdownResource(conn,preparedStatement,rs);}catch(SQLExceptionse){// 处理 JDBC 错误se.printStackTrace();}catch(Exceptione){// 处理 Class.forName 错误e.printStackTrace();}finally{shutdownResource(conn,preparedStatement,rs);}}publicstaticvoidshutdownResource(Connectionconn,Statementstmt,ResultSetrs){// 关闭资源try{if(rs!=null){rs.close();}}catch(SQLExceptionse1){//TODO}try{if(stmt!=null)stmt.close();}catch(SQLExceptionse2){//TODO}try{if(conn!=null)conn.close();}catch(SQLExceptionse){//TODO}}}运行结果
连接数据库...实例化PreparedStatement对象...[ID:5,first_name:wang5,last_name:david5,age:25]获取结果符合预期。
小白:线上系统使用jdbc时,因为使用jdbc的地方非常多,资源的释放很容易忘记,导致数据库资源不能释放,浪费数据库的性能。
扫地僧:确实如此,当我们注释掉上面的释放资源代码时,可以根据一些sql命令来查询mysql的连接,在navica客 户端上执行:
- 查看连接线程建立连接情况,数据如下:
show status like 'Threads%'; variable_name value Threads_cached 1 Threads_connected 4 Threads_created 5 Threads_running 2
- 查看具体的连接线程
SHOW PROCESSLIST; ID,USER,HOST,DB,COMMAND,TIME,STATE,INFO 4 event_scheduler localhost Daemon 7230 Waiting on empty queue 22 root localhost:6717 Sleep 1864 23 root localhost:6719 davidwang456 Sleep 1863 24 root localhost:6720 davidwang456 Sleep 1857 25 root localhost:6721 davidwang456 Query 0 starting SHOW PROCESSLIST
- 杀掉连接数,使用kill命令
示例:
kill 23扫地僧:还会其它的问题吗?
小白:分页时,使用JDBC会碰到要根据数据库的类型,写不同的sql,如mysql使用limit,oracle使用rownum进行分页,即不同类型数据库要写不同的实现。
扫地僧:除了上面的问题,还有:1. sql会分散到项目中的不同地方,难以管理 ; 2. 动态sql语句生产容易产生sql注入问题; 3. 如果想要高性能,需要自己实现一级甚至二级缓存;4. 代码量会增大,从而导致出bug的概率增大;5. 事务管理也需要开发者操心等等问题,这就导致了一些ORM框架的产生,比较流行的就有Hibernate,Mybatis,JPA,Spring Jdbc。现在来看,jdbc还香吗?
小白:我听说,最早流行的开发框架是S(Spring)S(Struts)H(Hibernate),后面流行S(Spring)S(Spring mvc)M(Mybatis),现在流行Spring boot和Spring Cloud微服务,甚至Service Mesh等框架了。那我们为什么选择Mybatis而不是Spring 自带的Spring jdbc呢?这样变成S(Spring)S(Spring mvc)S(Spring jdbc)全家桶岂不是更好?
扫地僧:好,那我们今天先讨论一下Spring Jdbc的优缺点吧?Mybatis,Hibernate,jpa留待我们以后讨论。
Spring JdbcTemplate
JdbcTemplate采用了模板方法的设计思想,和RowMapper配合,简化了jdbc访问。看下面的示例,
接着上面的项目:
添加依赖
注意上面pom.xml对spring依赖:
<dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>5.2.9.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>5.2.9.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.9.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.9.RELEASE</version></dependency>配置JdbcTemplate
<?xml version = "1.0" encoding = "UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"><!-- Initialization for data source --><beanid="dataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource"><propertyname="driverClassName"value="com.mysql.cj.jdbc.Driver"/><propertyname="url"value="jdbc:mysql://localhost:3306/davidwang456?characterEncoding=UTF-8&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC"/><propertyname="username"value="root"/><propertyname="password"value="wangwei456"/></bean><!-- Definition for studentJDBCTemplate bean --><beanid="jdbcTemplate"class="org.springframework.jdbc.core.JdbcTemplate"><propertyname="dataSource"ref="dataSource"/></bean></beans>测试程序
实体类,增加了create方法:
importjava.io.Serializable;importlombok.Data;@DatapublicclassStudentDTOimplementsSerializable{privatestaticfinallongserialVersionUID=1L;privateIntegerid;privateStringfirst_name;privateStringlast_name;privateIntegerage;@OverridepublicStringtoString(){returnthis.id+","+this.first_name+","+this.last_name+","+age;}publicstaticStudentDTOcreate(Stringfirst_name,Stringlast_name,Integerage){StudentDTOdto=newStudentDTO();dto.setAge(age);dto.setFirst_name(first_name);dto.setLast_name(last_name);returndto;}}测试类:
importjava.sql.ResultSet;importjava.sql.SQLException;importjava.util.Arrays;importjava.util.List;importjavax.sql.DataSource;importorg.springframework.context.ApplicationContext;importorg.springframework.context.support.ClassPathXmlApplicationContext;importorg.springframework.jdbc.core.RowMapper;importorg.springframework.jdbc.core.namedparam.MapSqlParameterSource;importorg.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;publicclassJdbcTemplateTest{publicstaticvoidmain(String[]args){queryBatch();}publicstaticvoidqueryBatch(){List<Integer>ids=Arrays.asList(5,6,8);MapSqlParameterSourceparameters=newMapSqlParameterSource();parameters.addValue("ids",ids);ApplicationContextcontext=newClassPathXmlApplicationContext("application.xml");DataSourcedataSource=(DataSource)context.getBean("dataSource");NamedParameterJdbcTemplatejdbcTemplate=newNamedParameterJdbcTemplate(dataSource);List<StudentDTO>list=jdbcTemplate.query("SELECT id,first_name,last_name,age FROM student WHERE id IN (:ids)",parameters,newRowMapper<StudentDTO>(){@OverridepublicStudentDTOmapRow(ResultSetresultSet,inti)throwsSQLException{StudentDTOstu=StudentDTO.create(resultSet.getString("first_name"),resultSet.getString("last_name"),resultSet.getInt("age"));stu.setId(resultSet.getInt("id"));returnstu;}});list.stream().forEach(System.out::println);}}运行结果如下:
5,wang5,david5,256,wang6,david6,258,wang8,david8,25从上面的示例可以看到JdbcTemplate解决了单纯使用JDBC而出现的大部分问题:
资源的获取与释放
支持不同类型的数据库,分页也可以通过spring-data的Pageable与Page实现。
事务管理也不需要开发者操心
代码量会减少,同时减少bug的概率;
但同时存在如下问题:
- sql会分散到项目中的不同地方,难以管理 ;
- 动态sql语句生产容易产生sql注入问题;
- 如果想要高性能,需要自己实现一级甚至二级缓存;
- 和Spring深度耦合,必须于Spring框架结合在一起使用。
而Mybatis,Hibernate,JPA都能解决上面的问题,故我们的选择不是Spring Jdbc。
总结
扫地僧:通过上面两个简单的示例,我们可以看出单纯使用jdbc和spring jdbcTemplate对复杂的生产环境来说,并不适用。特别重要的两点是:sql会分散到项目中的不同地方,难以管理;如果想要高性能,需要自己实现一级甚至二级缓存;它们对项目的管理和性能尤为重要。
小白:是不是意味着如果是演示类的项目,或者非常简单的项目,则可以使用jdbc或者Spring jdbcTemplate?例如使用jdbc时,可以将jdbc的公共代码抽取出来,组成一个JdbcUtil工具类,将执行的sql都封装成常量放到同一个文件(静态sql),通过调用jdbcUtil来执行,可以避免很多问题。
扫地僧:是的,对于一些临时项目或者短期项目,为了快速实现,可以选择jdbc来做。
扫地僧:最后一点,也是我最想告诉 你的是,开发时总会觉得规范这种东西离我们很远,但这些规范恰恰从根本上解决了我们的绝大部分问题,熟悉这些规范可以让我们在开发的路上走的更远,效率更高。JDBC规范的最新版本为JDBC 4.3 Specification,地址为:https://jcp.org/aboutJava/communityprocess/mrel/jsr221/index3.html,如果想要走的更远,请下载下来,一定要仔细研读一番它.
延伸阅读与资源
Java 工程师进阶:从 JVM 生产排障到OpenJDK原理
NumPy 从入门到生产落地:全链路实战指南(科学计算/向量化)
Redis 8 实战精讲:从 CRUD 到源码,构建高可用缓存系统
Redis 实战修炼与原理进阶
Python 3实战精进:从脚本到高并发订单引擎
python入门:Rquests从菜鸟脚本到企业级SDK的网络实战圣经
Milvus向量数据库实战修炼:从 0 到 1精通向量检索与生产落地
MongoDB 实战进阶与内核修炼
后端工程师的 AI 转型第一课:Ollama 与私有化大模型实战
10倍开发者的 Dify 魔法书:从零构建全栈 AI 应用
后端工程师转型AI第一课-Ollama 与私有化大模型实战
大型语言模型(LLM) vLLM 高性能推理落地实战
Agent开发之LlamaIndex 实战修炼与源码进阶
大语言模型Transformers 实战修炼与源码剖析