Spring的数据访问哲学
简介
当我们使用传统的JDBC操作数据库的时候,我们必须初始化数据访问框架、打开连接、处理各种异常和关闭连接。而Spring集成了多种数据访问技术。不管使用哪种,Spring都能帮我们消除持久化代码中那些单调枯燥的数据访问逻辑。我们可以依赖Spring来处理底层的数据访问,这样我们就可以专注于应用程序中数据的管理了。
了解Spring的数据访问异常体系
当使用 JDBC 与数据库进行交互遇见错误的时候,将会抛出名为 SQLException 的异常。如果不强制捕获SQLException的话,几乎无法使用JDBC做任何事情。SQLException表示在尝试访问数据库的时候出现了问题,但是这个异常却没有告诉你哪里出错了以及如何处理。
可能导致抛出SQLException的常见问题包括:
- 应用程序无法连接数据库;
- 要执行的查询存在语法错误;
- 查询中所使用的表和/或列不存在;
- 试图插入或更新的数据违反了数据库约束;
SQLException的问题在于捕获到它的时候该如何处理。事实上,能够触发SQLException的问题通常是不能在catch代码块中解决的。大多数抛出SQLException的情况表明发生了致命性错误。
如果无法从SQLException中恢复,那为什么我们还要强制捕获它呢?
即使对某些SQLException有处理方案,我们还是要捕获SQLException并查看其属性才能获知问题根源的更多信息。这是因为SQLException被视为处理数据访问所以问题的通用异常。对于所有的数据访问问题都会抛出SQLException,而不是对每种可能的问题都会有不同的异常类型。
Spring所提供的平台无关的持久化异常
不同于JDBC,Spring提供了多个数据访问异常,分别描述了它们抛出时所对应的问题。
从表中可以看出,Spring为读取和写入数据库的几乎所有错误都提供了异常。
看!不用写catch代码块
表中没有体现出来的一点就是这些异常都继承自DataAccessExecption。DataAccessExecption的特殊之处在于它时一个非检查型异常。换句话说,没有必要捕获Spring所抛出的数据访问异常(如果,你想的话也可以)。
为了利用Spring的数据访问异常,我们必须使用Spring所支持的数据访问模板。
数据访问模板化
Spring将数据访问过程中固定的和可变的部分明确划分为两个不同的类:模板(template)和回调(callback)。模板管理过程中固定的部分,而回调处理自定义的数据访问代码。
针对不同的持久化平台,Spring提供了多个可选的模板。
在声明模板和Repository之前,我们需要在Spring中配置一个数据源用来连接数据库。
配置数据源
Spring提供了在Spring上下文中配置数据源Bean的多种方式。
- 通过JDBC驱动程序定义的数据源;
- 通过JNDI查找的数据源;
- 连接池的数据源;
使用JNDI数据源(Java Nameing And Directory Interface,Java命名与目录接口)
Spring应用程序经常部署在Java EE应用服务器中,如Tomcat。这些服务器允许你配置通过JNDI获取数据源。这种配置的好处在于数据源完全可以在应用程序之外管理,这样应用程序只需要在访问数据库的时候查找数据源就可以了。
RootConfig.java
@Bean
public JndiObjectFactoryBean dataSource()
{
JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();
jndiObjectFB.setJndiName("jdbc/mysql"); //与配置的Resource的name属性一致
jndiObjectFB.setResourceRef(true); //引用
jndiObjectFB.setProxyInterface(DataSource.class); //设置代理接口,对应下面的type属性
return jndiObjectFB;
}
Tomcat中配置数据源
方式一(Context.xml)
<!--
|- name:表示以后要查找的名称。通过此名称可以找到DataSource,此名称可任意更换。
|- auth:由容器进行授权及管理,指的用户名和密码是否可以在容器上生效
|- type:此名称所代表的类型,现在为javax.sql.DataSource
|- maxActive:表示一个数据库在此服务器上所能打开的最大连接数
|- maxIdle:表示一个数据库在此服务器上维持的最小连接数
|- maxWait:最大等待时间。10000毫秒
|- username:数据库连接的用户名
|- password:数据库连接的密码
|- driverClassName:数据库连接的驱动程序
|- url:数据库连接的地址
-->
<Resource
name="jdbc/mysql"
auth="Container"
type="javax.sql.DataSource"
maxActive="100"
maxIdle="30"
maxWait="10000"
username="root"
password="123456"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/book"/>
方式二(server.xml)
打开server.xml配置文件,可以看到里面自带的一个全局JNDI配置,如下图所示:
编辑server.xml文件,添加全局JNDI数据源配置,配置如下:
<GlobalNamingResources>
<!-- Editable user database that can also be used by
UserDatabaseRealm to authenticate users
-->
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
<Resource
name="jdbc/mysql"
auth="Container"
type="javax.sql.DataSource"
maxActive="100"
maxIdle="30"
maxWait="10000"
username="root"
password="123456"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/book"/>
</GlobalNamingResources>
context.xml中添加引用
<ResourceLink name="jdbc/mysql" global="jdbc/mysql" type="javax.sql.DataSource"/>
注:conf配置文件里web.xml、server.xml、context.xml的区别
web.xml好像在你的每一个项目里也会有个web.xml,主要配置servlet这些的,如果在tomcat里的web.xml,应该对所有的项目都有效.
server.xml是对tomcat的设置,可以设置端口号,添加虚拟机这些的,是对服务器的设置
context应该也是正对项目的,你在server.xml中的每个虚拟机里host标签里都可以添加context标签,以表示该虚拟机对应哪一个项目
使用数据源连接池
方式一:Apache Commons DBCP
@Bean
public BasicDataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/book");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setInitialSize(5);
return dataSource;
}
方式二:c3p0
@Bean
public ComboPooledDataSource dataSource() throws PropertyVetoException {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/book");
ds.setUser("root");
ds.setPassword("123456");
return ds;
}
需导入c3p0的jar包。
基于JDBC驱动的数据源
Spring中,通过JDBC驱动定义数据源是最简单的配置方式。Spring提供了三个这样的数据源类(都位于org.springframework.jdbc.datasource包)供选择:
- DriverManagerDataSource: 每个连接请求都会返回一个新建的连接,没有进行池化管理;
- SimpleDriverDataSource: 与DriverManagerDataSource的工作方式相似,但是它直接使用JDBC驱动;
- SingleConnectionDataSource: 在每个连接请求时都会返回同一个连接。SingleConnectionDataSource不是严格意义上的连接池数据源,但是可以将其视为只有一个连接的池;
*@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/book");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
需要Spring-tx.jar 与Spring-jdbc.jar。
使用嵌入式的数据源
嵌入式数据库作为应用的一部分运行,而不是应用连接的独立数据库服务器。
@Bean
public DataSource dataSource()
{
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL) //指定数据库
.addScript("classpath:database/schme.sql")
.addScript("classpath:database/test-data.sql")
.build();
}
第一个引用了schme.sql,它包含了在数据库创建表的SQL;第二个引用了test-data.sql,用来将测试数据填充到数据库中。
这种方式我们也没有用过。
在Spring中使用JDBC
失控的JDBC代码
Connection conn = null;// connection
PreparedStatement preState = null;// prepared statement
ResultSet resultSet = null;// resultset
try {
// load driver
Class.forName("com.mysql.jdbc.Driver");
// get connection
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/ibatis_test", "root", "3713");
/* sql statement */
String querySql = "SELECT * FROM USERS WHERE USERNAME = ?";
// prepare statement
preState = conn.prepareStatement(querySql);
// set parameter
preState.setString(1, "michael");
// execute query
resultSet = preState.executeQuery();
// iterate result set
while (resultSet.next()) {
System.out.println(resultSet.getString("id") + "\t" + resultSet.getString("username") + "\t" + resultSet.getString("password"));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
// close streams seperately
try {
if (null != resultSet) {
resultSet.close();
}
if (null != preState) {
preState.close();
}
if (null != conn) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
这么多行就为了查询一条记录。
使用JDBC模板
Sprng的JDBC框架承担了资源管理和异常处理的工作,从而简化了JDBC代码。Sprng为JDBC提供了三个模板类供选择:
- JdbcTemplate:最基本的Sprng JDBC模板,支持简单的JDBC数据库访问功能以及基于索引参数的查询;
- NamedParameterJdbcTemplate:该模板类执行查询时可以将值以命名参数的形式绑定到SQL中,而不是简单的索引;
- SimpleJdbcTemplate:已经被废弃,不说了;
只有在你需要使用命名参数的时候,才需要使用NamedParameterJdbcTemplate。所以JdbcTemplate是最好的方案。
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource)
{
return new JdbcTemplate(dataSource);
}
@Autowired
private JdbcTemplate jdbcTemplate; //注入
增删改查操作
//插入
public void add(Spittle spittle)
{
String sql = "insert into t_admin(adminId,adminname,adminpwd) values ('1',?,?)";
jdbcTemplate.update(sql, new Object[]{spittle.getName(),spittle.getPassword()});
}
//删除
public void delete(String id)
{
String sql = "delete from t_admin where adminId = ?";
Object args[] = new Object[]{id};
jdbcTemplate.update(sql,args);
}
//更新
public void update(Spittle spittle)
{
String sql = "update t_admin set adminname = ? where adminId = ?";
Object args[] = new Object[]{spittle.getName(),spittle.getId()};
jdbcTemplate.update(sql,args);
}
//查询返回某一个值:查询表中数据总数
public void queryForOne()
{
String sql = "select count(*) from t_admin ";
int count = jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println("数据总数:" + count);
}
/**
* 功能:查询返回单个对象
* 步骤:新建MyRowMapper类实现RowMapper接口,重写mapRow方法,指定返回User对象
*/
public void queryForObject() {
String sql = "select * from t_admin where adminname= ?";
//新建MyRowMapper类实现RowMapper接口,重写mapRow方法,指定返回User对象
User user = jdbcTemplate.queryForObject(sql, new MyRowMapper(), "Tom");
System.out.println(user);
}
/**
* 功能:查询返回对象集合
* 步骤:新建MyRowMapper类实现RowMapper接口,重写mapRow方法,指定返回User对象
*/
public void queryForList() {
String sql = "select * from user";
//第三个参数可以省略
List<User> users = jdbcTemplate.query(sql, new MyRowMapper());
System.out.println(users);
}
其它方法需想要了解的话,可以百度看看。因为我的SQL语句写的特别烂,所以我都基本用Hibernate。
以上只是学习Spring实战所写的笔记,如有错误,请指正。谢谢