通过Spring和JDBC征服数据库
第10章 通过Spring和JDBC征服数据库
本章内容:
-
定义Spring对数据访问的支持
-
配置数据库资源
-
使用Spring的JDBC模版
在掌握了Spring容器的核心知识之后,是时候将它在实际应用中进行使用了。数据持久化是一 个非常不错的起点,因为几乎所有的企业级应用程序中都存在这样的需求。我们可能都处理 过数据库访问功能,在实际的工作中也发现数据访问有一些不足之处。我们必须初始化数据 访问框架、打开连接、处理各种异常和关闭连接。如果上述操作出现任何问题,都有可能损坏 或删除珍贵的企业数据。如果你还未曾经历过因未妥善处理数据访问而带来的严重后果,那 我要提醒你这绝对不是什么好事情。
做事要追求尽善尽美,所以我们选择了Spring。Spring自带了一组数据访问框架,集成了多种数 据访问技术。不管你是直接通过JDBC还是像Hibernate这样的对象关系映射(object-relational mapping,ORM)框架实现数据持久化,Spring都能够帮你消除持久化代码中那些单调枯燥的数 据访问逻辑。我们可以依赖Spring来处理底层的数据访问,这样就可以专注于应用程序中数据 的管理了。
10.1 Spring的数据访问哲学
从前面的几章可以看出,Spring的目标之一就是允许我们在开发应用程序时,能够遵循面向对 象(OO)原则中的“针对接口编程”。Spring对数据访问的支持也不例外。
像很多应用程序一样,Spittr应用需要从某种类型的数据库中读取和写入数据。为了避免持久 化的逻辑分散到应用的各个组件中,最好将数据访问的功能放到一个或多个专注于此项任务 的组件中。这样的组件通常称为数据访问对象(data access object,DAO)或Repository。
为了避免应用与特定的数据访问策略耦合在一起,编写良好的Repository应该以接口的方式暴 露功能。图10.1展现了设计数据访问层的合理方式。
服务对象 ——> repository接口 ——> repository实现
服务对象本身并不会处理数据访问,而是将数据访问委托给Repository。 Repository接口确保其与服务对象的松耦合
服务对象通过接口来访问Repository。这样做会有几个好处。第一,它使得服务对象 易于测试,因为它们不再与特定的数据访问实现绑定在一起。
实际上,你可以为这些数据访问 接口创建mock实现,这样无需连接数据库就能测试服务对象,而且会显著提升单元测试的效 率并排除因数据不一致所造成的测试失败。
此外,数据访问层是以持久化技术无关的方式来进行访问的。持久化方式的选择独立于 Repository,同时只有数据访问相关的方法才通过接口进行暴露。这可以实现灵活的设计,并 且切换持久化框架对应用程序其他部分所带来的影响最小。如果将数据访问层的实现细节渗 透到应用程序的其他部分中,那么整个应用程序将与数据访问层耦合在一起,从而导致僵化 的设计。
接口与Spring:如果在阅读了上面几段文字之后,你能感受到我倾向于将持久层隐藏在接口之 后,那很高兴我的目的达到了。我相信接口是实现松耦合代码的关键,并且应将其用于应用程序的各个层,而不仅仅是持久化层。还要说明一点,尽管Spring鼓励使用接口,但这并不是强 制的——你可以使用Spring将bean(DAO或其他类型)直接装配到另一个bean的某个属性中,而不需要一定通过接口注入。
为了将数据访问层与应用程序的其他部分隔离开来,Spring采用的方式之一就是提供统一的 异常体系,这个异常体系用在了它支持的所有持久化方案中。
10.1.1 了解Spring的数据访问异常体系
这里有一个关于跳伞运动员的经典笑话,这个运动员被风吹离正常路线后降落在树上并高高 地挂在那里。后来,有人路过,跳伞运动员就问他自己在什么地方。过路人回答说:“你在离地 大约20尺的空中。”跳伞运动员说:“你一定是个软件分析师。”过路人回应说“你说对了。你是 怎么知道的呢?”“因为你跟我说的话百分百正确,但丝毫用处都没有。”
__ 软件分析师表示不背锅
这个故事已经听过很多遍了,每次过路人的职业或国籍都会有所不同。但是这个故事使我想 起了JDBC中的SQLException。如果你曾经编写过JDBC代码(不使用Spring),你肯定会意识 到如果不强制捕获SQLException的话,几乎无法使用JDBC做任何事情。SQLException表 示在尝试访问数据库的时出现了问题,但是这个异常却没有告诉你哪里出错了以及如何进行 处理。
可能导致抛出SQLException的常见问题包括:
-
应用程序无法连接数据库;
-
要执行的查询存在语法错误;
-
查询中所使用的表和/或列不存在;
-
试图插入或更新的数据违反了数据库约束。
SQLException的问题在于捕获到它的时候该如何处理。事实上,能够触发SQLException 的问题通常是不能在catch代码块中解决的。大多数抛出SQLException的情况表明发生了致 命性错误。如果应用程序不能连接到数据库,这通常意味着应用不能继续使用了。类似地,如果查询时出现了错误,那在运行时基本上也是无能为力。
如果无法从SQLException中恢复,那为什么我们还要强制捕获它呢?
即使对某些SQLException有处理方案,我们还是要捕获SQLException并查看其属性才能获知问题根源的更多信息。这是因为SQLException被视为处理数据访问所有问题的通用异常。对于所有的数据访问问题都会抛出SQLException,而不是对每种可能的问题都会有不同的异常类型。
一些持久化框架提供了相对丰富的异常体系。例如,Hibernate提供了二十个左右的异常,分别 对应于特定的数据访问问题。这样就可以针对想处理的异常编写catch代码块。
即便如此,Hibernate的异常是其本身所特有的。正如前面所言,我们想将特定的持久化机制独 立于数据访问层。如果抛出了Hibernate所特有的异常,那我们对Hibernate的使用将会渗透到应用程序的其他部分。如果不这样做的话,我们就得捕获持久化平台的异常,然后将其作为平台 无关的异常再次抛出。
一方面,JDBC的异常体系过于简单了——实际上,它算不上一个体系。另一方面,Hibernate的异常体系是其本身所独有的。我们需要的数据访问异常要具有描述性而且又与特定的持久化框架无关。
__Hibernate 现在已经被弃用了
Spring所提供的平台无关的持久化异常
Spring JDBC提供的数据访问异常体系解决了以上的两个问题。不同于JDBC,Spring提供了多 个数据访问异常,分别描述了它们抛出时所对应的问题。表10.1对比了Spring的部分数据访问 异常以及JDBC所提供的异常。
从表中可以看出,Spring为读取和写入数据库的几乎所有错误都提供了异常。Spring的数据访 问异常要比表10.1所列的还要多。(在此没有列出所有的异常,因为我不想让JDBC显得太寒 酸。)
JDBC的异常 | Spring的数据访问异常 |
---|---|
BatchUpdateException DataTruncation SQLException | BadSqlGrammarException CannotAcquireLockException CannotSerializeTransactionException CannotGetJdbcConnectionException CleanupFailureDataAccessException ConcurrencyFailureExceptionBatchUpdateException DataAccessException DataRetrievalFailureException DataAccessResourceFailureException DataIntegrityViolationException ....... |
少 | 多 |
尽管Spring的异常体系比JDBC简单的SQLException丰富得多,但它并没有与特定的持久化方式相关联。这意味着我们可以使用Spring抛出一致的异常,而不用关心所选择的持久化方案。这有助于我们将所选择持久化机制与数据访问层隔离开来。
看!不用写catch代码块
表10.1中没有体现出来的一点就是这些异常都继承自DataAccessException。DataAccessException的特殊之处在于它是一个非检查型异常。换句话说,没有必要捕获Spring所抛出的数据访问异常(当然,如果你想捕获的话也是完全可以的)。
DataAccessException只是Sping处理检查型异常和非检查型异常哲学的一个范例。Spring认为触发异常的很多问题是不能在catch代码块中修复的。Spring使用了非检查型异常,而不是强制开发人员编写catch代码块(里面经常是空的)。这把是否要捕获异常的权力留给了开发人员。
为了利用Spring的数据访问异常,我们必须使用Spring所支持的数据访问模板。让我们看一下Spring的模板是如何简化数据访问的。
【】这类异常仅仅是提示,并没有捕获,catch内容需要自己来写,好像是即使有异常仍然放行,什么的
10.1.2 数据访问模板化
如果以前有搭乘飞机旅行的经历,你肯定会觉得旅行中很重要的一件事就是将行李从一个地方搬运到另一个地方。这个过程包含多个步骤。当你到达机场时,第一站是到柜台办理行李托运。然后保安人员对其进行安检以确保安全。之后行李将通过行李车转送到飞机上。如果你需要中途转机,行李也要进行中转。当你到达目的地的时候,行李需要从飞机上取下来并放到传送带上。最后,你到行李认领区将其取回。
尽管在这个过程中包含多个步骤,但是涉及到旅客的只有几个。承运人负责推动整个流程。你只会在必要的时候进行参与,其余的过程不必关心。这反映了一个强大的设计模式:模板方法模式。
【】设计模式:模板方法模式 就是子类继承接口和抽象类,只负责实现具体时机具体动作需要做的个性业务逻辑代码。通用部分与调用时机都由抽象类来负责。你只要存和去行李,其他的不用管,感觉这个比喻不恰当
模板方法定义过程的主要框架。在我们的示例中,整个过程是将行李从出发地运送到目的地。过程本身是固定不变的。处理行李过程中的每个事件都会以同样的方式进行:托运检查、运送到飞机上等等。在这个过程中的某些步骤是固定的——这些步骤每次都是一样的。比如当飞机到达目的地后,所有的行李被取下来并通过传送带运到取行李处。
在某些特定的步骤上,处理过程会将其工作委派给子类来完成一些特定实现的细节。这是过程中变化的部分。例如,处理行李是从乘客在柜台托运行李开始的。这部分的处理往往是在最开始的时候进行,所以它在处理过程中的顺序是固定的。由于每位乘客的行李登记都不一样,所以这个过程的实现是由旅客决定的。按照软件方面的术语来讲,模板方法将过程中与特定实现相关的部分委托给接口,而这个接口的不同实现定义了过程中的具体行为。
这也是Spring在数据访问中所使用的模式。不管我们使用什么样的技术,都需要一些特定的数据访问步骤。例如,我们都需要获取一个到数据存储的连接并在处理完成后释放资源。这都是在数据访问处理过程中的固定步骤,但是每种数据访问方法又会有些不同,我们会查询不同的对象或以不同的方式更新数据,这都是数据访问过程中变化的部分。
Spring将数据访问过程中固定的和可变的部分明确划分为两个不同的类:模板(template)和回调(callback)。模板管理过程中固定的部分,而回调处理自定义的数据访问代码。图展现了这两个类的职责。
repository模板 repository回调
1、准备资源
2、开始事务 ——> 3、在事务中执行
5、提交/回滚事务 <—— 4、返回数据
6、关闭资源和处理错误
如图所示,Spring的模板类处理数据访问的固定部分——事务控制、管理资源以及处理异常。同时,应用程序相关的数据访问——语句、绑定参数以及整理结果集——在回调的实现中处理。事实证明,这是一个优雅的架构,因为你只需关心自己的数据访问逻辑即可。
针对不同的持久化平台,Spring提供了多个可选的模板。如果直接使用JDBC,那你可以选择JdbcTemplate。如果你希望使用对象关系映射框架,那HibernateTemplate或JpaTemplate可能会更适合你。表列出了Spring所提供的所有数据访问模板及其用途。
模板类(org.springframework.*) | 用 途 |
---|---|
jdbc.core.JdbcTemplate | JDBC连接 |
jdbc.core.namedparam.NamedParameterJdbcTemplate | 支持命名参数的JDBC连接 |
orm.hibernate3.HibernateTemplate | Hibernate 3.x以上的Session |
orm.ibatis.SqlMapClientTemplate | iBATIS SqlMap客户端 |
orm.jdo.JdoTemplate | Java数据对象(Java Data Object)实现 |
orm.jpa.JpaTemplate | Java持久化API的实体管理器 |
Spring为多种持久化框架提供了支持,这里没有那么多的篇幅在本章对其进行一一介绍。因此,我会关注于我认为最为实用的持久化方案,这也是读者最可能用到的。
在本章中,我们将会从基础的JDBC访问开始,因为这是从数据库中读取和写入数据的最基本方式。在第11章中,我们将会了解Hibernate和JPA,这是最流行的基于POJO的ORM方案。我们会在第12章结束Spring持久化的话题,在这一章中,将会看到Spring Data项目是如何让Spring支持无模式数据的。
但首先要说明的是Spring所支持的大多数持久化功能都依赖于数据源。因此,在声明模板和Repository之前,我们需要在Spring中配置一个数据源用来连接数据库。
10.2 配置数据源
无论选择Spring的哪种数据访问方式,你都需要配置一个数据源的引用。Spring提供了在Spring上下文中配置数据源bean的多种方式,包括:
-
通过JDBC驱动程序定义的数据源;
-
通过JNDI查找的数据源;
-
连接池的数据源。
对于即将发布到生产环境中的应用程序,我建议使用从连接池获取连接的数据源。如果可能的话,我倾向于通过应用服务器的JNDI来获取数据源。请记住这一点,让我们首先看一下如何配置Spring从JNDI中获取数据源。
10.2.1 使用JNDI数据源
Spring应用程序经常部署在Java EE应用服务器中,如WebSphere、JBoss或甚至像Tomcat这样的Web容器中。这些服务器允许你配置通过JNDI获取数据源。这种配置的好处在于数据源完全可以在应用程序之外进行管理,这样应用程序只需在访问数据库的时候查找数据源就可以了。
另外,在应用服务器中管理的数据源通常以池的方式组织,从而具备更好的性能,并且还支持系统管理员对其进行热切换。
利用Spring,我们可以像使用Spring bean那样配置JNDI中数据源的引用并将其装配到需要的类中。位于jee命名空间下的元素可以用于检索JNDI中的任何对象(包括数据源)并将其作为Spring的bean。例如,如果应用程序的数据源配置在JNDI中,我们可以使用元素将其装配到Spring中,如下所示:
如果想使用Java配置的话,那我们可以借助JndiObjectFactoryBean从JNDI中查找DataSource:
显然,通过Java配置获取JNDI bean要更为复杂。大多数情况下,Java配置要比XML配置简单,但是这一次我们需要写更多的Java代码。但是,很容易就能够看出Java代码中与XML相对应的配置,Java配置的内容其实也不算多。
10.2.2 使用数据源连接池
如果你不能从JNDI中查找数据源,那么下一个选择就是直接在Spring中配置数据源连接池。尽管Spring并没有提供数据源连接池实现,但是我们有多项可用的方案,包括如下开源的实现:
-
Apache Commons DBCP (http://jakarta.apache.org/commons/dbcp);
-
BoneCP (http://jolbox.com/) 。
Java配置的话,连接池形式的DataSourcebean可以声明如下:
/**
* 配置数据源 注入到Spring中
* @return
* @throws SQLException
*/
@Bean
public DataSource dataSource() throws SQLException {
// BasicDataSource ds = new BasicDataSource();
DruidDataSource ds = new DruidDataSource(); // 将读取出来的数据设置到 DruidDataSource 中
ds.setDriverClassName(druidSettings.getJdbcDriverClassName());
ds.setUrl(druidSettings.getDruidUrl());
ds.setUsername(druidSettings.getUsername());
ds.setPassword(druidSettings.getPassword());
ds.setInitialSize(druidSettings.getInitialSize());
ds.setMinIdle(druidSettings.getMinIdle());
ds.setMaxActive(druidSettings.getMaxActive());
ds.setTimeBetweenEvictionRunsMillis(druidSettings.getTimeBetweenEvictionRunsMillis());
ds.setMinEvictableIdleTimeMillis(druidSettings.getMinEvictableIdleTimeMillis());
ds.setValidationQuery(druidSettings.getValidationQuery());
ds.setTestWhileIdle(druidSettings.isTestWhileIdle());
ds.setTestOnBorrow(druidSettings.isTestOnBorrow());
ds.setTestOnReturn(druidSettings.isTestOnReturn());
ds.setPoolPreparedStatements(druidSettings.isPoolPreparedStatements());
ds.setMaxPoolPreparedStatementPerConnectionSize(druidSettings.getMaxPoolPreparedStatementPerConnectionSize());
ds.setFilters(druidSettings.getFilters());
ds.setConnectionProperties(druidSettings.getConnectionProperties());
return ds;
}
前四个属性是配置BasicDataSource所必需的。属性driverClassName指定了JDBC驱动类的全限定类名。在这里我们配置的是H2数据库的数据源。属性url用于设置数据库的JDBCURL。最后,username和password用于在连接数据库时进行认证。
以上四个基本属性定义了BasicDataSource的连接信息。除此以外,还有多个配置数据源连接池的属性。表10.3列出了DBCP BasicDataSource最有用的一些池配置属性
BasicDataSource的池配置属性
池配置属性 | 所指定的内容 |
---|---|
initialSize | 池启动时创建的连接数量 |
maxActive | 同一时间可从池中分配的最多连接数。如果设置为0,表示无限制 |
maxIdle | 池里不会被释放的最多空闲连接数。如果设置为0,表示无限 |
minIdle | 在不创建新连接的情况下,池中保持空闲的最小连接数 |
maxWait | 在抛出异常之前,池等待连接回收的最大时间(当没有可用连接时)。如果设置为-1,表示无限等待 |
minEvictableIdleTimeMillis | 连接在池中保持空闲而不被回收的最大时间 |
poolPreparedStatements | 是否对预处理语句(prepared statement)进行池管理(布尔值) |
maxOpenPreparedStatements | 在同一时间能够从语句池中分配的预处理语句(prepared statement)的最大数量。如果设置为0,表示无限制 |
10.2.3 基于JDBC驱动的数据源
在Spring中,通过JDBC驱动定义数据源是最简单的配置方式。Spring提供了三个这样的数据源类(均位于org.springframework.jdbc.datasource包中)供选择:
-
DriverManagerDataSource:在每个连接请求时都会返回一个新建的连接。与DBCP的BasicDataSource不同,由DriverManagerDataSource提供的连接并没有进行池化管理;
-
SimpleDriverDataSource:与DriverManagerDataSource的工作方式类似,但是它直接使用JDBC驱动,来解决在特定环境下的类加载问题,这样的环境包括OSGi容器;
-
SingleConnectionDataSource:在每个连接请求时都会返回同一个的连接。尽管SingleConnectionDataSource不是严格意义上的连接池数据源,但是你可以将其视为只有一个连接的池。
以上这些数据源的配置与DBCPBasicDataSource的配置类似。例如,如下就是配置DriverManagerDataSource的方法:
与具备池功能的数据源相比,唯一的区别在于这些数据源bean都没有提供连接池功能,所以没有可配置的池相关的属性。
尽管这些数据源对于小应用或开发环境来说是不错的,但是要将其用于生产环境,你还是需要慎重考虑。因为SingleConnectionDataSource有且只有一个数据库连接,所以不适合用于多线程的应用程序,最好只在测试的时候使用。而DriverManagerDataSource和SimpleDriverDataSource尽管支持多线程,但是在每次请求连接的时候都会创建新连接,这是以性能为代价的。鉴于以上的这些限制,我强烈建议应该使用数据源连接池。
10.2.4 使用嵌入式的数据源
除此之外,还有一个数据源是我想对读者介绍的:嵌入式数据库(embedded database)。嵌入式数据库作为应用的一部分运行,而不是应用连接的独立数据库服务器。尽管在生产环境的设置中,它并没有太大的用处,但是对于开发和测试来讲,嵌入式数据库都是很好的可选方案。 这是因为每次重启应用或运行测试的时候,都能够重新填充测试数据。
H2数据库比较坑,就不学了,现在用的也少
10.2.5 使用profile选择数据源
多种在Spring中配置数据源的方法,我相信你已经找到了一两种适合你的应用程序的配置方式。实际上,我们很可能面临这样一种需求,那就是在某种环境下需要其中一种数据源,而在另外的环境中需要不同的数据源。
例如,对于开发期来说,元素是很合适的,而在QA环境中,你可能希望使用DBCP的BasicDataSource,在生产部署环境下,可能需要使用。
对于hap说来是用几个profile来分开配置sit uat prd下的环境,打包时选择不同的环境,本地启动时需要配置
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<profile.env>dev</profile.env>
</properties>
</profile>
<profile>
<id>sit</id>
<activation/>
<properties>
<profile.env>sit</profile.env>
</properties>
</profile>
<profile>
<id>uat</id>
<activation/>
<properties>
<profile.env>uat</profile.env>
</properties>
</profile>
</profiles>
10.3 在Spring中使用JDBC
持久化技术有很多种,而Hibernate、iBATIS和JPA只是其中的几种而已。尽管如此,还是有很多的应用程序使用最古老的方式将Java对象保存到数据库中:他们自食其力。不,等等,这是他们挣钱的途径。这种久经考验并证明行之有效的持久化方法就是古老的JDBC。
为什么不采用它呢?JDBC不要求我们掌握其他框架的查询语言。它是建立在SQL之上的,而SQL本身就是数据访问语言。此外,与其他的技术相比,使用JDBC能够更好地对数据访问的性能进行调优。JDBC允许你使用数据库的所有特性,而这是其他框架不鼓励甚至禁止的。
再者,相对于持久层框架,JDBC能够让我们在更低的层次上处理数据,我们可以完全控制应用程序如何读取和管理数据,包括访问和管理数据库中单独的列。这种细粒度的数据访问方式在很多应用程序中是很方便的。例如在报表应用中,如果将数据组织为对象,而接下来唯一要做的就是将其解包为原始数据,那就没有太大意义了。
但是JDBC也不是十全十美的。虽然JDBC具有强大、灵活和其他一些优点,但也有其不足之处。
10.3.1 应对失控的JDBC代码
如果使用JDBC所提供的直接操作数据库的API,你需要负责处理与数据库访问相关的所有事情,其中包含管理数据库资源和处理异常。如果你曾经使用JDBC往数据库中插入数据,那如下代码对你应该并不陌生:
private static final String SQL_INSERT_SPITTER = "insert into spitter (username, password, first_name) values (?, ?, ?)";
private DataSource dataSource;
public void addSpitter(Spitter spitter) {
Connection conn = null;
PreparedStatement stmt = null;
try {
/**
* 1、获取连接
* 2、创建语句
* 3、绑定参数
* 4、执行语句
*/
conn = dataSource.getConnection();
stmt = conn.prepareStatement(SQL_INSERT_SPITTER);
stmt.setString(1, spitter.getUsername());
stmt.setString(2, spitter.getPassword());
stmt.setString(3, spitter.getFirstName());
stmt.execute();
} catch (SQLException e) {
e.printStackTrace();
// 处理异常
} finally {
try {
// 清理资源
if (stmt !=null) {
stmt.close();
}
if (conn == null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
传统的JDBC来更新数据库中Spitter表的一行。查询 省略。。。。
10.3.2 使用JDBC模板
Spring的JDBC框架承担了资源管理和异常处理的工作,从而简化了JDBC代码,让我们只需编写从数据库读写数据的必需代码。
正如前面小节所介绍过的,Spring将数据访问的样板代码抽象到模板类之中。Spring为JDBC提供了三个模板类供选择:
-
JdbcTemplate:最基本的Spring JDBC模板,这个模板支持简单的JDBC数据库访问功能以及基于索引参数的查询;
-
NamedParameterJdbcTemplate:使用该模板类执行查询时可以将值以命名参数的形式绑定到SQL中,而不是使用简单的索引参数;
-
SimpleJdbcTemplate:该模板类利用Java 5的一些特性如自动装箱、泛型以及可变参数列表来简化JDBC模板的使用。
以前,在选择哪一个JDBC模板的时候,我们需要仔细权衡。但是从Spring 3.1开始,做这个决定变得容易多了。SimpleJdbcTemplate已经被废弃了,其Java 5的特性被转移到了JdbcTemplate中,并且只有在你需要使用命名参数的时候,才需要使用NamedParameterJdbcTemplate。这样的话,对于大多数的JDBC任务来
说,JdbcTemplate就是最好的可选方案,这也是本小节中所关注的方案。
使用JdbcTemplate来插入数据
为了让JdbcTemplate正常工作,只需要为其设置DataSource就可以了,这使得在Spring中配置JdbcTemplate非常容易,如下面的@Bean方法所示:
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
DataSource是通过构造器参数注入进来的,将jdbcTemplate装配到Repository中并使用它来访问数据库
@Repository
public class JdbcSpitterRepository implements SpittleRepository {
private JdbcOperations jdbcOperations;
@Inject
public JdbcSpitterRepository(JdbcOperations jdbcOperations) {
this.jdbcOperations = jdbcOperations;
}
}
在这里,JdbcSpitterRepository类上使用了@Repository注解,这表明它将会在组件扫描的时候自动创建。它的构造器上使用了@Inject注解,因此在创建的时候,会自动获得一个JdbcOperations对象。JdbcOperations是一个接口,定义了JdbcTemplate所实现的操作。通过注入JdbcOperations,而不是具体的JdbcTemplate,能够保证 JdbcSpitterRepository通过JdbcOperations接口达到与JdbcTemplate保持松耦合。
public Spitter save(Spitter spitter) {
jdbcTemplate.update(
"insert into Spitter (username, password, first_name, last_name, email)" +
" values (?, ?, ?, ?, ?)",
spitter.getUsername(),
spitter.getPassword(),
spitter.getFirstName(),
spitter.getLastName(),
spitter.getEmail());
return spitter;
}
@Override
public void save(Spitter spitter) {
jdbcOperations.update(SQL_INSERT_SPITTER,
spitter.getUsername(),
spitter.getPassword(),
spitter.getFirstName());
}
在JdbcTemplate中使用Java 8的Lambda表达式
public Spitter findById(Long id) {
return jdbcOperations.queryForObject(
"select id, username,password,first_name, last_name, email from spitter where id=? ",
(rs, rowNum) -> {return new Spitter(rs.getLong("id"),
rs.getString("username"),
rs.getString("passsword"),
rs.getString("first_name"),
rs.getString("last_name"),
rs.getString("email"));
},
id);
}
此处查询过程中遇到个小小的问题,也是纠结很久,报错信息大概是
Request processing failed; nested exception is java.lang.IllegalArgumentExce
参数异常,不能转化为spitter等
我解决方式从数据映射开始,看是否查出结果,是否可以从结果中取出值,是否可以转成spitter,[图片上传失败...(image-5b69e1-1557068358284)]
并没有问题,此时才明白了是没有返回json格式,是springmvc的问题,有添加了两个fastjson的jar包就可以了,对自己也是呵呵一笑
private static final String SQL_INSERT_SPITTER = "insert into spitter (username, password, first_name, last_name, email) values (?, ?, ?, ?, ?)";
private static final String SQL_UPDATE_SPITTER = "update spitter set username = ?, password = ?, first_name = ?, last_name = ?, email = ?" +
"where id = ?";
private static final String SQL_INSERT_SPITTER_PARAM = "insert into spitter (username, password, first_name, last_name, email)" +
" values (:username, :password, :first_name, :last_name, :email)";
private static final String SQL_UPDATE_SPITTER_ROLE = "update spitter set ROLE_USER = ?, enabled = ?" +
"where id = ?";
public void addSpitter(Spitter spitter) {
// 绑定参数
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("username", spitter.getUsername());
paramMap.put("password", spitter.getPassword());
paramMap.put("first_name", spitter.getFirstName());
paramMap.put("last_name", spitter.getLastName());
paramMap.put("email", spitter.getEmail());
jdbcOperations.update(SQL_INSERT_SPITTER_PARAM, paramMap);
KeyHolder keyHolder = new GeneratedKeyHolder();
//jdbcTemplate.update(new PreparedStatementCreator() {
// @Override
// public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
// PreparedStatement ps = (PreparedStatement) connection.prepareStatement(SQL_INSERT_SPITTER, Statement.RETURN_GENERATED_KEYS);
// ps.setObject(1, spitter.getUsername());
// ps.setObject(2, spitter.getPassword());
// ps.setObject(3, spitter.getFirstName());
// ps.setObject(4, spitter.getLastName());
// ps.setObject(5, spitter.getEmail());
// return ps;
// }
//}, keyHolder);
paramMap.put("role_name", "ROLE_USER");
paramMap.put("enabled", "true");
jdbcOperations.update(SQL_UPDATE_SPITTER_ROLE, paramMap);
// jdbcOperations.update(SQL_UPDATE_SPITTER_ROLE, "ROLE_USER", "true", keyHolder.getKey().intValue());
}
这个版本的addSpitter()比前一版本的代码要长一些。这是因为命名参数是通过java.util.Map来进行绑定的。不过,每行代码都关注于往数据库中插入Spitter对象。这个方法的核心功能并不会被资源管理或异常处理这样的代码所充斥。
这个例子,没有执行成功,而选择了传统的?方式执行的,注释部分为可执行代码
10.4 小结
数据是应用程序的血液。有些数据中心论者甚至主张数据即应用。鉴于数据的重要地位,以健壮、简单和清晰的方式开发应用程序的数据访问部分就显得举足轻重了。 在Java中,JDBC是与关系型数据库交互的最基本方式。但是按照规范,JDBC有些太笨重了。 Spring能够解除我们使用JDBC中的大多数痛苦,包括消除样板式代码、简化JDBC异常处理,你所需要做的仅仅是关注要执行的SQL语句。 在本章中,我们学习了Spring对数据持久化的支持,以及Spring为JDBC所提供的基于模板的抽象,它能够极大地简化JDBC的使用。
杂谈
这章虽然相对基础,没有精髓地方没有列出,比如说batchupdate等
补充知识
查询多条数据
public List<Spitter> findByUsername(String username) {
List<Spitter> spitters = this.jdbcTemplate.query(
"select id, username,password,first_name, last_name, email from spitter where username=? ",
(rs, rowNum) -> {return new Spitter(rs.getLong("id"),
rs.getString("username"),
rs.getString("password"),
rs.getString("first_name"),
rs.getString("last_name"),
rs.getString("email"));
},
username);
return spitters;
}
jdbc批量操作
public int[] batchUpdate(final List<Spitter> spitters) {
List<Object[]> batch = new ArrayList<Object[]>();
for (Spitter spitter : spitters) {
Object[] values = new Object[] {
spitter.getUsername(),
spitter.getPassword(),
spitter.getFirstName(),
spitter.getLastName(),
spitter.getEmail()};
batch.add(values);
}
String SQL_INSERT_SPITTER = "insert into spitter (username, password, first_name, last_name, email) values (?, ?, ?, ?, ?)";
int[] updateCounts = jdbcTemplate.batchUpdate(
SQL_INSERT_SPITTER,
batch);
return updateCounts;
}
mybatis的批量操作
<insert id="batchInsert" parameterType="java.util.List">
insert into words_result(id,wc,wordBg,wordEd, wordsName, wp, alter_native,text_id,date_time)
values
<foreach collection="list" item="item" index="index" separator=",">
(#{item.id,jdbcType=INTEGER},
#{item.wc,jdbcType=VARCHAR},
#{item.wordBg,jdbcType=VARCHAR},
#{item.wordEd,jdbcType=VARCHAR},
#{item.wordsName,jdbcType=VARCHAR},
#{item.wp,jdbcType=VARCHAR},
#{item.alterNative,jdbcType=VARCHAR},
#{item.textId,jdbcType=INTEGER},
#{item.dateTime})
</foreach>
</insert>
<update id="batchUpdate" parameterType="java.util.Map">
<foreach collection="list" separator=";" item="item">
update words_result set
<if test="item.wc != null">
wc = #{item.wc,jdbcType=VARCHAR},
</if>
<if test="item.wordBg != null">
wordBg = #{item.wordBg,jdbcType=VARCHAR},
</if>
<if test="item.wordEd != null">
wordEd = #{item.wordEd,jdbcType=VARCHAR},
</if>
<if test="item.wordsName != null">
wordsName = #{item.wordsName,jdbcType=VARCHAR},
</if>
<if test="item.wp != null">
wp = #{item.wp,jdbcType=VARCHAR},
</if>
<if test="item.alterNative != null">
alter_native = #{item.alterNative,jdbcType=VARCHAR},
</if>
<if test="item.textId != null">
text_id = #{item.textId,jdbcType=INTEGER},
</if>
<if test="item.dateTime != null">
update_date = #{item.dateTime,jdbcType=VARCHAR}
</if>
where id = #{item.id,jdbcType=INTEGER}
</foreach>
</update>
spring security 配置权限
/**
* 从数据库中读取用户和权限信息
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws AuthenticationException {
System.out.println("加载Security。。。读取权限");
// 启用内存用户储存
/* auth.inMemoryAuthentication()
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser("user").password("1").roles("USER").and()
.withUser("admin").password("1").roles("USER","ADMIN");*/
try {
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select username, password, enabled from spitter where username = ?")
.authoritiesByUsernameQuery("select username, ROLE_USER from spitter where username = ?");
} catch (Exception e) {
e.printStackTrace();
}
}
——2019/05/05 于成都完成