#数据访问
- 数据库访问,JDBC
- Spring事务管理
- ORM整合
##DAO
- Data Access Object
数据访问对象 - 数据访问相关接口
在使用过程中,会有DAO接口,针对DAO接口具有不同的实现。但是业务对象,使用DAO的对象通过接口的形式来使用DAO对象。在DAO层可以有不同种类的实现,比如JDBC实现,也可以整合其他的ORM框架,例如MyBatis、Hibernate实现。以接口的方式进行实现,可以屏蔽掉底层的细节,让业务对象根据接口使用DAO。
##ORM
- Object Relation Mapping
- 对象关系映射
数据库记录到Java对象的映射关系,自动的把数据库记录转化为对应的Java代码,也可以把Java代码转化成对应的数据库记录。
#Why Spring JDBC?
##数据访问 使用JDBC的方式去访问数据的过程的步骤
- 连接参数
连接什么地址,用户名密码是什么 - 打开连接
创建到目标数据库的连接 - 声明SQL语句以及参数
- 执行SQL,并循环访问结果
- 执行业务
针对每行的访问结果执行特定的业务 - 处理异常
- 关闭连接,语句以及结果集
关闭资源
这些步骤里面,业务里面真正需要关心的是连接参数:必须去关心,没有连接参数就没办法进行连接;声明SQL语句与参数:直接和业务需求相关,例如查询某种用户的结果集,或者符合某种条件的结果集,不声明SQL无法执行结果;执行业务:循环访问结果对于用户来说,如果有某种方式来告诉每行记录都是什么样子的,只要根据结果集做执行对应的业务就可以。
从用户的角度来看,只需要做这三样事情,就可以去完成业务需求,对于其他的底层实现,如何打开连接、如何访问结果、如何处理异常关闭资源,从用户角度来说并没有对业务产生很大的影响。Spring JDBC的作用就是封装这些用户不关心的底层细节内容。只要关心的内容,通过接口暴露出来,只需要关心编写对应的业务。Spring JDBC提高编程效率,关系业务细节。
##DataSource Spring里面是使用DataSource类接口进行代表的。DataSource代表一个数据源,如下内容就是数据源所需要的参数。
- 驱动类名
- 连接地址
- 用户名称
- 用户密码
通过这几个参数可以构成一个DataSource,根据特定的需求定义成不同的数据源,比如可以定义成MySQL数据源,也可以定义成Oracle的数据源。
DataSource方法唯一的基础方法就是 getConnection,比如我们建立一个DataSource的连接,我们获取连接以后就可以做数据查询相关的事情,例如查询SQL,创建表
##DataSource DataSource是接口,并不是由Spring提供的,而是由Java本身的JavaEE提供的接口,而Spring只是在这个基础之上提供了一个实现DriverManagerDatasource
,基础JDBC提供的DataSource。但是这个实现有一个问题,就是每次使用时都会创建一次连接。在一些场景下,例如频繁查询都会创建一次连接,从性能上来讲并不是很好,所以就会有类似线程池一样的数据库连接池的概念,但是这个Spring本身并没有提供,而是由Apache Commons DBCP提供的BasicDataSource
的实现。BasicDataSource
是在org.apache.commons.dbcp
包下实现类。BasicDataSource
实现了数据库连接池的功能,这样就不需要每次查询的时候,都去创建一个连接,实现连接的复用
##BasicDataSource配置 BasicDataSource配置模板
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="testOnBorrow" value="false" />
<property name="testWhileIdle" value="true" />
<!-- 连接池启动时的初始值 -->
<property name="initialSize" value="10" />
<!-- 连接池的最大值 -->
<property name="maxActive" value="100" />
<!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
<property name="maxIdle" value="50" />
<!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
<property name="minIdle" value="10" />
<!--#给出一条简单的sql语句进行验证-->
<property name="validationQuery" value="select getdate()" />
<!--#在取出连接时进行有效验证-->
<property name="removeAbandonedTimeout" value="120" />
<property name="removeAbandoned" value="true" />
<!-- #运行判断连接超时任务的时间间隔,单位为毫秒,默认为-1,即不执行任务。 -->
<property name="timeBetweenEvictionRunsMillis" value="3600000" />
<!-- #连接的超时时间,默认为半小时。 -->
<property name="minEvictableIdleTimeMillis" value="3600000" />
</bean>
</beans>
通过db.properties获取数据库连接
<context:property-placeholder location="db.properties" />
上面这种方式是比较简单的加载方式,效果等同于下面的方式
<bean id = "headerProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigure">
<property name="location" value="classpath:db.properties" />
</bean>
##JdbcTemplate 声明SQL及参数、执行SQL及处理结果、异常处理都封装在叫做JdbcTemplate
的类里面,所在的包为org.springframework.jdbc.core
。
##JdbcTemplate与JDBC差别 例如,下面的代码查询了SQL有多少行,这里通过queryForObject方法执行SQL语句,并把结果集转换成Integer.class。
String sql = "SELECT COUNT(*) FROM user";
int rowCount = this.jdbcTemplate.queryForObject(sql, Integer.class);
也可以查询某批用户的数量,这里可以传入SQL的参数
String sql = "SELECT COUNT(*) FROM user WHERE frist_name = ?";
int countOfNamedJoe = this.jdbcTemplate.queryForObject(sql,Integer.class,"Joe");
我们还可以插入其他类型,比如查找某个用户id的lastName是什么,大家可以看到,我们把参数放到了Object[]当中。
String sql = "SELECT COUNT(*) FROM user WHERE id = ?";
String lastName = this.jdbcTemplate.queryForObject(sql,new Object[]{1212L},String.class);
**注意:**上面两个例子的参数放置是相反的, JdbcTemplate提供了很多查询相关插入相关的接口,一般都会具有两种不同类型的接口,比如返回参数类型放到前面查询参数放到后面,另外一种刚好相反。 主要原因是因为Java函数获取不确定参数个数的时候,是在最后的参数添加...
##JdbcTemplate增改删方法 ###新增,插入
String sql = "INSERT INTO user (first_name,last_name) values (? ,?)";
this.jdbcTemplate.update(sql,"Meimei","Han");
###更新
String sql = "UPDATE user SET last_name=? WHERE id = ?";
this.jdbcTemplate.update(sql, "Li",5276L);
###删除
String sql = "DELETE FROM user WHERE id = ?";
this.jdbcTemplate.update(sql, Long.valueOf(userId));
###创建表格
String sql = "create table user (id integer,first_name varchar(100),last_name varchar(100))";
this.jdbcTemplate.execute(sql);
##JdbcTemplate 转换对象 ORM是把数据库的信息转换成对象,JdbcTemplate也是可以完成该功能的。
JdbcTemplate提供了接口叫做RowMapper,可以通过RowMapper把ResultSet一一的转换成你所需要的对象,例如下面的例子,只有一行数据,id为1212的用户,返回了User的Java对象。
String sql = "SELECT first_name,last_name FROM user WHERE id = ?";
User user = this.jdbcTemplate.queryForObject(sql,new Object[]{1212L},new RowMapper<User>(){
public User mapRow(ResultSet rs,int rowNum) throws SQLException{
User user = new User();
user.setFirstName(rs.getString("first_name"));
user.setLastName(rs.getString("last_name"));
}
});
也可以像如下方法返回List<User>
String sql = "SELECT first_name,last_name FROM user";
List<User> users = this.jdbcTemplate.queryForObject(sql,new RowMapper<User>(){
public User mapRow(ResultSet rs,int rowNum) throws SQLException{
User user = new User();
user.setFirstName(rs.getString("first_name"));
user.setLastName(rs.getString("last_name"));
}
});
###总结JdbcTemplate使用起来比较简单,没有打开连接,转换结果集,关闭连接,释放资源等操作。
##定义JdbcTemplate ###Java代码形式定义JDBCTemplate 在设置DataSource的时候创建JdbcTemplate
public class JdbcExampleDao implements ExampleDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource){
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
// ... DAO 接口实现
}
###XML声明使用JdbcTemplate
<bean id="exampleDao" class="com.netease.course.JdbcExampleDao" >
<property name= "dataSource" ref="dataSource" />
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<context:property-placeholder location="jdbc.properties" />
###通过Annotation(注解)方式配置JdbcTemplate @Repository这个代表是DAO的Bean,是对数据进行了访问,@Autowired来进行自动的注入
@Repository
public class JdbcExampleDao implements ExampleDao {
private JdbcTemplate jdbcTemplate;
@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
// ... DAO 接口实现
}
##代码演示JdbcTemplate的使用 maven的基本依赖,jdbcTemplate的基本依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
添加数据库配置文件db.properties
jdbc.driverClassName= com.mysql.jdbc.Driver
jdbc.url= jdbc:mysql://192.168.1.200:3306/example
jdbc.username=root
jdbc.password=
在Spring配置文件配置数据源
<context:component-scan base-package="com.hava.spring_data" />
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<context:property-placeholder location="db.properties" />
测试用的DAO
package com.hava.spring_data.repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.sql.DataSource;
/**
* Created by yanfa on 2016/10/24.
*/
@Repository
public class MyJdbcTemplateDao {
private JdbcTemplate jdbcTemplate;
@Autowired
public void setDataSource(DataSource dataSource){
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void createTable()
{
System.out.println("ceateTable");
String sql = "CREATE TABLE user (id INTEGER,first_name VARCHAR(100), last_name VARCHAR(100))";
jdbcTemplate.execute(sql);
}
}
主执行类
package com.hava.spring_data;
import com.hava.spring_data.repository.MyJdbcTemplateDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by yanfa on 2016/10/24.
*/
public class SpringJdbcMain {
public static void main(String[] args) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
MyJdbcTemplateDao jdbcTemplateDao = context.getBean("myJdbcTemplateDao", MyJdbcTemplateDao.class);
jdbcTemplateDao.createTable();
((ConfigurableApplicationContext) context).close();
}
}
执行时发生异常如下:
java.sql.SQLException: Access denied for user 'root'@'192.168.1.100' (using password: NO)
由于密码错误,更正密码则运行正确
jdbcTemplate插入数据
public void insertData(){
this.jdbcTemplate.update("INSERT INTO user VALUES (1, ?, ?)", "Meimie","Han");
this.jdbcTemplate.update("INSERT INTO user VALUES (1, ?, ?)", "Lei","Li");
}
jdbcTemplate获取行数
public int count(){
String sql = "SELECT COUNT(*) FROM user";
return this.jdbcTemplate.queryForObject(sql,Integer.class);
}
执行结果
ceateTable
2
##SQL结果转换成Java对象 Entity对象类
package com.hava.spring_data.entity;
/**
* Created by yanfa on 2016/10/24.
*/
public class User {
private int id;
private String first_name;
private String last_name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirst_name() {
return first_name;
}
public void setFirst_name(String first_name) {
this.first_name = first_name;
}
public String getLast_name() {
return last_name;
}
public void setLast_name(String last_name) {
this.last_name = last_name;
}
}
jdbcTemplate查询
public List<User> getUserList(){
return this.jdbcTemplate.query("SELECT * FROM user", new RowMapper<User>() {
@Override
public User mapRow(ResultSet resultSet, int rowNumber) throws SQLException {
User user = new User();
user.setId(resultSet.getInt("id"));
user.setFirst_name(resultSet.getString("first_name"));
user.setLast_name(resultSet.getString("last_name"));
return user;
}
});
}
结果正确
##NamedParameterJdbcTemplate 在插入数据,可以通过?的形式插入参数。但是有很多列,每列都是?无法确定意义。由此,Spring提供了NamedParameterJdbcTemplate,不在通过?形式定义参数,而是通过命名的形式
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Autowired
public void setDataSource(DataSource dataSource){
//this.jdbcTemplate = new JdbcTemplate(dataSource);
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int countOfUsersByFirstName(String firstName){
String sql = "SELECT COUNT(*) FROM user WHERE first_name = :firstName;";
Map<String ,String> nameParameters = Collections.singletonMap("firstName",firstName);
return this.namedParameterJdbcTemplate.queryForObject(sql,nameParameters,Integer.class);
}
运行成功
##NamedParameterJdbcTemplate的接口形式
querForObject(String sql,Map<String,?> paramMap,RowMapper<T> rowMapper);
querForObject(String sql,SqlParameterSource paramSource,RowMapper<T> rowMapper);
还有SqlParameterSrouce的接口,通改接口可以实现实现自己的实现逻辑
##SqlParameterSource 在Spring就有简单的实现,最简单是MapSqlParameterSource,使用上和之前是类似的。还提供了另外一种比较方便的形式BeanPropertySqlParameterSource,如果是对象的话,我们使用map就比较麻烦,而使用BeanPropertySqlParameterSource就简单的多。这些Spring对SqlParameterSource的实现都是在org.springframework.jdbc.core.namedparam
包里面。
##BeanPropertySqlParameterSource的使用 使用BeanPropertySqlParameterSource,则需要有Entity对象类 在使用时,我们需要直接传入对象,而不需要传入Map。
public int countOfUsersByFirstName(User user){
String sql = "SELECT COUNT(*) FROM user WHERE first_name = :first_name;";
SqlParameterSource sqlParameterSource = new BeanPropertySqlParameterSource(user);
return this.namedParameterJdbcTemplate.queryForObject(sql,sqlParameterSource,Integer.class);
}
**注意:**这里的User的属性,必须和所填入注入的SQL参数的名称保持一致。这里的SQL查询和上一个的查询SQL并不一致。
##异常处理 SQL的异常时checked的异常,要么捕获异常,要么在接口抛出异常。但是无法连接、语法错误、表列不存在,都会影响应用的正常使用,不是正常状态。
Spring是把SQLException转换到了DataAccessException,DataAccessException是unchecked的异常。写代码时不会try-catch。不用再代码中散布异常捕获。在业务处理的时候,在最终要返回业务的节点做处理。如果不能回复则给用户进行返回
##DataAccessException 在Spring中DataAccessException是基类,有很多异常处理都是基于DataAccessException。我们可以根据子类的不同名称判断错误类型。