JavaEE程序设计【七】

五、Spring-数据库


1、Spring JDBC

Spring JDBC模块主要由4个包组成,分别是

  • core(核心包)
  • dataSource(数据源包)
  • object(对象包)
  • support(支持包)。

(1)JdbcTemplate

JdbcTemplate类是Spring JDBC的核心(core)类。JdbcTemplate类的直接父类是JdbcAccessor,该类提供了一些访问数据库时使用的公共属性。

  • DataSource:其主要功能是获取数据库连接,还可以引入对数据库连接的缓冲池和分布式事务的支持,它可以作为访问数据库资源的标准接口。
  • SQLExceptionTranslator: 该接口负责对SQLException进行转译工作。通过必要的设置获取SQLExceptionTranslator中的方法,可以使JdbcTemplate在需要处理SQLException时,委托SQLExceptionTranslator的实现类来完成相关的转译工作。

JdbcOperations接口定义了在JdbcTemplate类中可以使用的操作集合

继承JdbcAccessor:获取DataSource、SQLExceptionTranslator 属性,能进行数据库连接和异常抛出

实现JdbcOperations接口:实现增删改查功能。

(2)Spring JDBC配置

1.配置数据源dataSource

2.配置JDBC模板jdbcTemplate

  • 注入数据源

3.配置需要实例化的Bean

  • 注入JDBC模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/javaee"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>

<bean id="xxx" class="Xxx">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>

注意:

关于上述示例dataSource配置中的4个属性说明

driverClassName:所使用的驱动名称,对应驱动AR包中的 Driver类

url:数据源所在地址

username:访问数据库的用户名

password:访问数据库的密码.

  • dataSource:org.springframework.jdbc.datasource.DriverManagerDataSource
  • jdbcTemplate:`org.springframework.jdbc.core.JdbcTemplate`

dataSource—>注入-jdbcTemplate—>注入-实例化的Bean

property-placeholder提取数据库配置参数

一、问题描述:

1、有些参数在某些阶段中是常量,比如:

(1)在开发阶段我们连接数据库时的连接url、username、password、driverClass等

(2)分布式应用中client端访问server端所用的server地址、port,service等

(3)配置文件的位置

2、而这些参数在不同阶段之间又往往需要改变,比如:在项目开发阶段和交付阶段数据库的连接信息往往是不同的,分布式应用也是同样的情况。

二、期望

能不能有一种解决方案可以方便我们在一个阶段内不需要频繁书写一个参数的值,而在不同阶段间又可以方便的切换参数配置信息

三、解决

spring3中提供了一种简便的方式就是<context:property-placeholder>元素

方法一:只需要在spring的配置文件里添加一句:

1
2
<!--引入数据库配置信息 -->
<context:property-placeholder location="classpath:jdbc.properties"/>

情况2配置多个

1
2
3
4
5
6
7
8
  <bean id="propertyConfigure" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:/opt/demo/config/demo-db.properties</value>
<value>classpath:/opt/demo/config/demo-db2.properties</value>
</list>
</property>
</bean>

即可,这里location值为参数配置文件的位置,参数配置文件通常放在src目录下,而参数配置文件的格式跟java通用的参数配置文件相同,即键值对的形式,例如:

1
2
3
4
5
#jdbc配置
test.jdbc.driverClassName=com.mysql.jdbc.Driver
test.jdbc.url=jdbc:mysql://localhost:3306/test
test.jdbc.username=root
test.jdbc.password=root

四、应用

1.这样一来就可以为spring配置的bean的属性设置值了,比如spring有一个jdbc数据源的类DriverManagerDataSource

在配置文件里这么定义bean:

1
`<bean id=``"testDataSource"` `class``=``"org.springframework.jdbc.datasource.DriverManagerDataSource"``>``    ``<property name=``"driverClassName"` `value=``"${test.jdbc.driverClassName}"``/>``    ``<property name=``"url"` `value=``"${test.jdbc.url}"``/>``    ``<property name=``"username"` `value=``"${test.jdbc.username}"``/>``    ``<property name=``"password"` `value=``"${test.jdbc.password}"``/>``</bean>`

甚至可以将${ }这种形式的变量用在spring提供的注解当中,为注解的属性提供值

其他详阅:spring配置文件引入properties文件:context:property-placeholder标签使用总结

(3)jdbcTemplate方法使用

Spring JdbcTemplate方法详解

  1. execute()方法:可用于执行任何sql语句,但是一般用来执行DDL语句;

  2. update()以及batchUpdate()方法:update()方法用来执行增加、修改和删除等语句;batchUpdate()方法用来执行批处理相关的语句;

  3. query()以及queryForXxx():用来执行查询相关的语句;

  4. call()方法:用于执行存储过程、函数相关的语句;

1
2
3
4
5
6
7
8
9
10
11
12
13
//1.查询一行数据并返回int型结果  
jdbcTemplate.queryForInt("select count(*) from test");
//2. 查询一行数据并将该行数据转换为Map返回
jdbcTemplate.queryForMap("select * from test where name='name5'");
//3.查询一行任何类型的数据,最后一个参数指定返回结果类型
jdbcTemplate.queryForObject("select count(*) from test", Integer.class);
//4.查询一批数据,默认将每行数据转换为Map
jdbcTemplate.queryForList("select * from test");
//5.只查询一列数据列表,列类型是String类型,列名字是name
jdbcTemplate.queryForList("
select name from test where name=?", new Object[]{"name5"}, String.class);
//6.查询一批数据,返回为SqlRowSet,类似于ResultSet,但不再绑定到连接上
SqlRowSet rs = jdbcTemplate.queryForRowSet("select * from test");

JdbcTemplate类支持的回调类

  • 预编译语句及存储过程创建回调:用于根据JdbcTemplate提供的连接创建相应的语句;

PreparedStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的PreparedStatement;

CallableStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的CallableStatement;

  • 预编译语句设值回调:用于给预编译语句相应参数设值;

PreparedStatementSetter:通过回调获取JdbcTemplate提供的PreparedStatement,由用户来对相应的预编译语句相应参数设值;

BatchPreparedStatementSetter:;类似于PreparedStatementSetter,但用于批处理,需要指定批处理大小;

  • 自定义功能回调:提供给用户一个扩展点,用户可以在指定类型的扩展点执行任何数量需要的操作;

ConnectionCallback:通过回调获取JdbcTemplate提供的Connection,用户可在该Connection执行任何数量的操作;

StatementCallback:通过回调获取JdbcTemplate提供的Statement,用户可以在该Statement执行任何数量的操作;

PreparedStatementCallback:通过回调获取JdbcTemplate提供的PreparedStatement,用户可以在该PreparedStatement执行任何数量的操作;

CallableStatementCallback:通过回调获取JdbcTemplate提供的CallableStatement,用户可以在该CallableStatement执行任何数量的操作;

  • 结果集处理回调:通过回调处理ResultSet或将ResultSet转换为需要的形式;

RowMapper:用于将结果集每行数据转换为需要的类型,用户需实现方法mapRow(ResultSet rs, int rowNum)来完成将每行数据转换为相应的类型。

RowCallbackHandler:用于处理ResultSet的每一行结果,用户需实现方法processRow(ResultSet rs)来完成处理,在该回调方法中无需执行rs.next(),该操作由JdbcTemplate来执行,用户只需按行获取数据然后处理即可。

ResultSetExtractor:用于结果集数据提取,用户需实现方法extractData(ResultSet rs)来处理结果集,用户必须处理整个结果集;

PreparedStatement和CallableStatement区别 :

Statement接口提供了执行语句和获取结果的基本方法, PreparedStatement继承自Statement, 而CallableStatement继承自PreparedStatement

PreparedStatement   接口添加了处理 IN 参数的方法

CallableStatement  接口添加了处理 OUT 参数的方法

PreparedStatement:

  • 可变参数的SQL,编译一次,执行多次,效率高;
  • 安全性好,有效防止Sql注入等问题;
  • 支持批量更新,批量删除;

CallableStatement:

  • 继承自PreparedStatement,支持带参数的SQL操作;

  • 支持调用存储过程,提供了对输出和输入/输出参数(INOUT)的支持;

  • Statement每次执行sql语句,数据库都要执行sql语句的编译 ,最好用于仅执行一次查询并返回结果的情形时,效率高于PreparedStatement。

PreparedStatement是预编译的,使用PreparedStatement有几个好处

  1. 在执行可变参数的一条SQL时,PreparedStatement比Statement的效率高,因为DBMS预编译一条SQL当然会比多次编译一条SQL的效率要高。
  2. 安全性好,有效防止Sql注入等问题。
  3. 对于多次重复执行的语句,使用PreparedStament效率会更高一点,并且在这种情况下也比较适合使用batch;
  4. 代码的可读性和可维护性。

注:

executeQuery:返回结果集(ResultSet)。

executeUpdate: 执行给定SQL语句,该语句可能为 INSERT、UPDATE 或 DELETE 语句, 或者不返回任何内容的SQL语句(如 SQL DDL 语句)。

execute: 可用于执行任何SQL语句,返回一个boolean值,表明执行该SQL语句是否返回了ResultSet。如果执行后第一个结果是ResultSet,则返回true,否则返回false。

(4)RowMapper的分析解释

RowMapper的分析解释

RowMapper是什么?怎么用?

Spring中JdbcTemplate使用RowMapper(简单明了的代码)

sping中的RowMapper可以将数据中的每一行数据封装成用户定义的类。

我们在数据库查询中,如果返回的类型是用户自定义的类型(其实我们在数据库查询中大部分返回的都是自定义的类)则需要包装,如果是Java自定义的类型,如:String则不需要。

如果sping与hibernate 相结合了,基本上是用不到,大多数都是在spring单独使用时用到,常见的情况就是与JdbcTemplate一起使用。

可以通过建立内部类实现RowMapper接口,RowMapper中有一个mapRow方法,所以实现RowMapper接口一定要实现mapRow方法,而对自定义类的包装就在mapRow方法中实现。

org.springframework.jdbc.core

接口RowMapper

所有已知实现类

1
2
3
4
5
6
7
BeanPropertyRowMapper,
ColumnMapRowMapper,
MappingSqlQueryWithParameters.
RowMapperImpl,
SingleColumnRowMapper,
UpdatableSqlQuery.
RowMapperImpl
实现mapRow
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
46
47
48
49
50
51
52
53
54
55
package com.cxl.demo.dao;  

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import com.cxl.demo.entity.User;
public class UserDaoImpl {
private JdbcTemplate jdbcTemplate;

public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public List<User> getUserByName(String username) {
String sql = "select * from t_user where username = ?";
Object[] params = new Object[] { username };
List<User> users = null;
/**
* 使用接口实现类
*/
users = jdbcTemplate.query(sql, params, new UserRowMapper());
/**
* 使用匿名内部类
* 如果UserRowMapper类只使用一次,单独为其创建一个类多余,可以使用匿名类
* 省略了书写一个实现类
*/
users = jdbcTemplate.query(sql, params,
new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
return user;
}
});
return (users != null && users.size() > 0) ? users : null;
}

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.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
return user;
}

}
}
BeanPropertyRowMapper

BeanPropertyRowMapper使用注意事项

BeanPropertyRowMapper/ParameterizedBeanPropertyRowMapper ,如果PO和数据库模型的字段完全对应(字段名字一样或者驼峰式与下划线式对应),如果使用JdbcTemplate则可以使用这个RowMapper作为PO和数据库的映射

2、事务封装

Spring用于事务管理的依赖包:spring-tx-4.3.6.RELEASE的JAR包

在该JAR包的org.springframework.transaction包中,有3个接口文件PlatformTransactionManager、TransactionDefinition和TransactionStatus

  • PlatformTransactionManager:Spring提供的平台事务管理器,主要用于管理事务。该接口中提供了三个事务操作的方法,具体如下:

    • TransactionStatus getTransaction(TransactionDefinition definition);//用于获取事务状态信息
    • void commit(TransactionStatus status);//用于提交事务
    • void rollback(TransactionStatus status);//用于回滚事务
    • PlatformTransactionManager接口只是代表事务管理的接口,并不知道底层是如何管理事务的,具体如何管理事务则由它的实现类来完成。该接口常见的几个实现类如下
    • 补充:多数据源必须用DataSourceTransactionManager
  • TransactionDefinition:TransactionDefinition接口是事务定义(描述)的对象,该对象中定义了事务规则,并提供了获取事务相关信息的方法

      • 事务的传播行为是指在同一个方法中,不同操作前后所使用的事务。传播行为有很多种,具体如下表所示 (“传播”建议理解:事务何时和如何创建)

      • 在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务,通常情况下,数据的查询不会影响原数据的改变,所以不需要进行事务管理,而对于数据的插入、更新和删除操作,必须进行事务管理。如果没有指定事务的传播行为,Spring默认传播行为是REQUIRED。

  • TransactionStatus:接口是事务的状态,它描述了某一时间点上事务的状态信息。该接口中包含6个方法,具体如下

Spring事务管理分两种方式

  • 编程式事务管理
    • 通过编写代码实现的事务管理,包括定义事务的开始、正常执行后的事务提交和异常时的事务回滚
  • 声明式事务管理
    • 通过AOP技术实现的事务管理,主要思想是将事务作为一个“切面”代码单独编写,然后通过AOP技术将事务管理的“切面”植入到业务目标类中

声明式事务管理最大的优点在于开发者无需通过编程的方式来管理事务,只需在配置文件中进行相关的事务规则声明,就可以将事务应用到业务逻辑中。这使得开发人员可以更加专注于核心业务逻辑代码的编写,在一定程度上减少了工作量,提高了开发效率,所以在实际开发中,通常都推荐使用声明式事务管理。

(1)基于XML方式的声明式事务

基于XML方式的声明式事务是在配置文件中通过tx:advice元素配置事务规则来实现的。当配置了事务的增强处理后,就可以通过编写的AOP配置,让Spring自动对目标生成代理。tx:advice元素及其子元素如下图所示

配置tx:advice元素的重点是配置tx:method子元素,上图中使用灰色标注的几个属性是tx:method元素中的常用属性。其属性描述具体如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<bean id="accountDao" class="cn.edu.ccc.ch5.jdbc.AccountDaoImpl">		
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression="execution(* cn.edu. ch5.jdbc.*.*(..))" id="txPointCut" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" />
</aop:config>

(2) 基于Annotation方式的声明式事务

1、在Spring容器中注册事务注解驱动;

1
2
3
4
5
...
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>

2、在需要事务管理的类或方法上使用@Transactional注解。

如果将注解添加在Bean类上,则表示事务的设置对整个Bean类的所有方法都起作用;如果将注解添加在Bean类中的某个方法上,则表示事务的设置只对该方法有效。

使用@Transactional注解时,可以通过参数配置事务详情:

例如

1
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false)

(3)其他深入

一文带你认识Spring事务

透彻的掌握 Spring 中 @transactional 的使用

-----------------------本文结束 感谢阅读-----------------------
坚持原创技术分享,您的支持将鼓励我继续创作!恰饭^.^~