《第5章 征服数据库》
几个概念
数据持久化
- 将对象永久保存到数据库中
- 可以减少访问数据库的次数
ORM
- ORM(Object Relational Mapping),对象关系映射
- 通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。
- 本质上就是将数据从一种形式转换到另外一种形式
- 是使用对象操作数据库的设计思想
JPA
- JAP(Java Persistence API),Java持久化API
- 是一套规范,应用于对象关系映射(ORM)
- JPA仅为关系数据库管理系统提供持久化特性
Hibernate
- 是Java的一个开源的对象关系映射(ORM)框架
- 版本3.2及更高版本提供了JPA的实现
DAO
- DAO(Data Access Object),数据访问对象
- 为某种类型的数据库或其他持久性机制提供一个抽象接口的对象
- 通过映射应用程序对持久层的调用,DAO提供一些特定的数据操作,而无需暴露数据库细节。
iBATIS
- 于2010年6月16号被谷歌托管,改名为MyBatis
- 是一个基于SQL映射支持Java和.NET的持久层框架
- iBATIS提供的持久层框架包括SQL Maps和Data Access Object
Spring的数据访问哲学
DAO提供了数据读取和写入到数据库的一种方式
它以接口的方式发布功能
应用程序通过接口来进行数据访问
举个例子:假设DAO发布了一个接口,该接口声明了将对象存储到数据库的方法save
然后有一个用户服务类,它实现了该接口
那么,应用程序想要将对象存储到数据库,那么直接调用用户服务类save方法即可
OK,那么我们再回过头来看,用户服务类实际上是一个持久化的过程
因为是用户服务类实现了把对象存储到数据中,那么这个实现可以有多种选择
可以是JDBC,Hibernate、JPA或者其他任意的持久化框架
但我们在调用时只需要看接口声明的方法即可,不管它是怎么实现的
即便持久化的实现方式发生了改变,也不影响对save方法的调用
换句话说,DAO实际上是独立于持久化实现的
一言以蔽之,数据访问独立于持久化机制
1.Spring的数据访问异常体系
JDBC的SQLException表示在尝试访问数据库时出现了问题
但是这个异常却没有告诉你哪里出错了以及如何进行处理
一些持久化框架提供了相对丰富的异常体系,但它是某个框架所特有的
Spring JDBC提供的数据访问异常体系解决了以上两个问题
不同于JDBC,Spring提供了多个数据访问异常,分别描述了它们抛出时所对应的问题
而且它并没有与特定的持久化方式相关联
这些异常都是继承自DataAccessException
DataAccessException的特殊之处在于它是一个非检查性异常
即没有必要捕获Spring所抛出的数据访问异常
2.数据访问模板化
Spring将数据访问过程中固定的和可变的部分明确为两个不同的类:
模板(template)和回调(callback)
模板管理过程中固定的部分,而回调处理自定义的数据访问代码
配置数据源
Spring所支持的大多数持久化功能都依赖于数据源
Spring提供了在Spring上下文中配置数据源Bean的多种方式
- 提供JDBC驱动程序定义的数据源
- 通过JNDI查找的数据源
- 连接池的数据源
1.使用JNDI数据源
Spring应用程序经常部署在Java EE应用服务器中,如WebSphere,JBoss或者Tomcat
这时配置JNDI来获取数据源
利用Spring,我们可以像使用Spring Bean那样配置JNDI中数据源的引用并将其装配到需要的类中
位于jee命名空间下的<jee:jndi-lookup>元素可以用于检索JNDI的任何对象并将其用于Spring Bean中
如果应用程序的数据源配置在JNDi中,则可以使用<jee:jndi-lookup>元素并将其装配到Spring中
<jee:jndi-lookup id="dataSource"
jndi-name="..."
resource-ref="true"/>
jndi-name用于指定JNDI中资源的名称
2.使用数据连接池
Spring本身没有提供数据源连接池的实现,
第三方Apache的DBCP是个不错的选择
需要导入commons-pool2-2.8.0.jar,commons-dbcp2-2.7.0.jar
DBCP包含了多个提供连接池功能的数据源,其中BasicDataSource是最常用的
使用mysql数据库,具体配置如下:
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/USR?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
BasicDataSource是可以对连接池的属性进行配置的
3.基于JDBC驱动的数据源
在Spring中,通过JDBC驱动定义数据源是最简单的配置方式
Spring提供了两种数据源对象供选择
- DriverManagerDataSource:在每个连接请求时都会返回一个新建的链接
- SingleConnectionDataSource:在每次连接请求时都会返回同一个连接
使用JDBC驱动定义的数据源如下,使用的数据库为mysql:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/USR?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
与之前的DHCP相比,差别不大,主要是数据源不一样
以及DriverManagerDataSource没有提供连接池功能
在Spring中使用JDBC
持久化技术有很多种,如Hibernate,MyBait和JPA等等
JDBC是最古老的一种方式:将Java对象保存到数据库中
JDBC其实还是很有竞争优势的:
- 不需要掌握其他框架的查询语言
- 可以更好地对数据访问的性能调优
- 允许用户使用数据库的所有特性
- 可以在更低层次上处理数据
如果直接使用JDBC所提供的直接操作数据库的API
那么就需要负责处理与数据库访问相关的所有事情
而且代码冗长,甚至复杂
Spring将数据访问的样板式代码提取到模板类中
Spring为JDBC提供了2个模板类共供使用:
- JdbcTemplate: 最基本的Spring JDBC模板
- NamedParameterJdbcTemplate: 可以将查询值以命令参数的形式绑定到SQL中
- SimpleJdbcTemplate:已不再使用,其功能已被前两个模板类覆盖
Spring的JDBC框架的例子,使用JdbcTemplate模板类
准备一个数据库的表:
CREATE TABLE t_user_main (
userId int(10) DEFAULT NULL COMMENT '用户id,作为主键',
userName varchar(50) DEFAULT NULL COMMENT '用户名',
age int(3) DEFAULT NULL COMMENT '年龄'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
定义一个User类,类的属性与数据库表中定义的列一一对应
public class User {
private int ID;
private String Name;
private int age;
/*所有属性的set和get方法*/
}
接着定义DAO的接口,假设该接口拥有两个功能
public interface UserDAO {
//将对象存储到数据库
void save(User user);
//从数据库中读取数据
List<User> getUser();
}
然后编写DAO接口的实现类,并在Spring的XML文件中对其定义
public class UserDAOImpl implements UserDAO{
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(DataSource dataSource){
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
//通过insert 语句来达到将对象保存到数据库的目的
public void save(User user) {
jdbcTemplate.update("insert into t_user_main(userID ,userName,age) values (?,?,?)",
new Object[]{user.getID(),user.getName(),user.getAge()},
new int[]{Types.INTEGER,Types.VARCHAR,Types.INTEGER});
}
@Override
//通过select语句来达到读取数据的目的
public List<User> getUser() {
//RowMapper是执行查找操作的回调接口
//通过实现该接口,可对其返回的结果集进行操作
List<User> list = jdbcTemplate.query("select * from t_user_main", new RowMapper<User>() {
@Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
User user = new User();
user.setID(resultSet.getInt("userID"));
user.setName(resultSet.getString("userName"));
user.setAge(resultSet.getInt("age"));
return user;
}
});
return list;
}
}
然后将其在XML文件中进行定义
<bean id="user" class="UserDAOImpl">
<property name="jdbcTemplate" ref="dataSource"/>
</bean>
如此就可以在模块中调用此接口来进行数据业务的处理,而不用关心此接口的具体实现类是哪个类
实现了DAO与持久化机制的独立性
在未引入NamedParameterJdbcTemplate模板之前
我们需要留意查询中参数的顺序,将值传递给update等方法时要保证正确的顺序
引入NamedParameterJdbcTemplate模板之后
参数的顺序就不重要了
命名参数可以赋予SQL中的每个参数一个明确的名字
在调用update等语句时就通过名字来引用参数
JDBC是访问关系型数据库的最基本方式
Spring的JDBC模板能够把我们从处理样板式代码的泥沼中解救出来
我们只需要关心实际的数据查询和更新即可