预讲内容
1、如何利用Spring进行拦截对象的注入;
2、利用jdbcTemplate实现数据表的CRUD操作;
3、使用JdbcDaoSuppot支持处理
具体内容
Spring开发框架除了提供有整合一系列第三方开发框架的功能之外,也提供有自己的一套完整的处理结构。它可以自己处理控制层、自己处理业务层、自己处理数据层、等等一系列的处理。
7.1、JdbcTemplate的产生动机
首先必须要明确一点,任何情况下,在Java里面只要是进行数据库的开发操作,那么永恒就只会有一个技术:JDBC,而所有的开发框架都是针对于JDBC的包装,像Hibernate时对JDBC的重度包装,包装的结果是用户几乎感觉不到任何数据库操作的身影。就连一些负载的SQL查询,Hibernate都变为了HQL,其目的就是告诉用户————不需要知道JDBC的存在。
而MyBatis/IBatis就属于中度包装,而现在要学习的JDBC操作模板就属于轻度包装
传统JDBC开发
开发步骤:1、进行数据库驱动程序的加载;
2、取得数据库的连接对象;
3、声明要操作的SQL语句(需要使用预处理);
4、创建数据操作对象;
5、执行SQL语句;
6、处理返回的操作结果(ResultSet);
7、关闭结果集对象;
8、关闭数据库的操作对象(Statement);
9、如果执行的是更新则应该进行事务提交或回滚;
10、关闭数据库连接。
优点:1、具备固定的操作流程,代码结构简单;
2、JDBC是一个Java的公共服务,属于标准;
3、由于没有涉及到过于复杂对象操作,所以性能是最高的;
缺点:1、代码的冗余度太高,每次都需要编写大量的重复操作;
2、用户需要自己手工进行事务的处理操作;
3、所有的操作必须严格按照既定的步骤执行;
4、如果出现了执行的异常,则需要用户自己处理;
使用Spring的JDBC模板开发
开发步骤:1、声明要操作的SQL语句(需要使用预处理);
2、执行SQL语句;
3、处理返回的操作结果(ResultSet);
(其他的都由框架自动完成)
优点:1、代码简单,但是不脱离JDBC形式;
2、由于有SpringAOP的支持,用户只关心核心;
3、对于出现的程序异常可以采用统一的方式进行处理;
4、与JDBC的操作步骤或形式几乎相同;
缺点:1、与重度包装的Hibernate框架不同,不够智能化;
2、处理返回结果的时候不能自动转化为vo类对象,需要由用户手工处理结果集。
综合来讲,Spring中的JdbcTemplate成为了JDBC与Hibernate之间的过渡操作,如果不想使用那么庞大的Hibernate或者不想使用最繁琐的JDBC,就选择JdbcTemplate进行操作。
JdbcTemplate由于不涉及到复杂操作,所以与JDBC相比,性能几乎相当。
7.2、连接数据库
如果要使用JDBC模板操作,那么首先必须要解决的就是数据库的连接问题,如果进行连接,那么需要个特定的操作类。
如果要想使用JDBC的操作处理程序类,那么首先需要在确立Spring项目的时候添加相应的开发包。
public DriverManagerDataSource(java.lang.String url,
java.lang.String username,
java.lang.String password)
范例:数据库创建脚本
删除数据库
DROP DATABASE IF EXISTS springdb;
使用数据库
DROP DATABASE springdb CHARACTER SET UTF8;
使用数据库
USE springdb
删除数据表
DROP TABLE IF EXISTS news;
创建数据表
CREATE TABLE news(
nid INT AUTO_INCREMENT,//自动增长
title VARCHAR(50),
pubdata DATE,
content TEXT,
CONSTRAINT pk_nid PRIMARY KEY(nid)
);
随后需要JDBC连接,那么就需要配置数据的驱动程序
要进行数据的连接可以使用:“org.springframework.jdbc.datasource
Class DriverManagerDataSource“在这个类之中提供有如下的操作方法:
·构造方法:DriverManagerDataSource();
·构造方法:DriverManagerDataSource(java.lang.String url,java.lang.String username,java.lang.String password)
·设置驱动程序:setDriverClassName(java.lang.String driverClassName)
·设置数据库连接地址:public void setUrl(@Nullable java.lang.String url)
·设置用户名:public void setUsername(@Nullable java.lang.String username)
·设置密码:public void setPassword(@Nullable java.lang.String password)
范例:取得数据库连接
public static void main(String[] args) throws Exception{
//数据库的连接必须首先处理(固定的)
DriverManagerDataSource source =new DriverManagerDataSource();
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setUrl("jdbc:mysql://localhost:3306/springdb");
source.setUsername("root");
source.setPassword("config_mysql");
Connection connection = source.getConnection();
System.out.println(connection);
}
此时就可以实现数据库的连接,也就是说利用了DriverManagerDataSource类避免了Class类以及DriverManager类的使用,同时最重要的是发现,在利用DriverManagerDataSource操作时取得的连接对象依然是java.sql.Connection。
7.3、实现CRUD操作
取得连接固然是一件非常兴奋的事情,但是最终依然要落实在表数据的操作上,如果要想操作数据表中的数据,则可以使用“org.springframework.jdbc.core.JdbcTemplate”。那么子啊这个类中提供有如下方法:
·构造方法:JdbcTemplate(javax.sql.DataSource dataSource);
·无参构造:JdbcTemplate();
·设置数据源:setDataSource(@Nullable javax.sql.DataSource dataSource);
·更新操作:update(java.lang.String sql,
@Nullable
java.lang.Object... args)
throws DataAccessException;
·更新并取得ID:update(PreparedStatementCreator psc,
KeyHolder generatedKeyHolder)
throws DataAccessException;
·查询全部操作:public <T> java.util.List<T> query(java.lang.String sql,@Nullable java.lang.Object[] args,RowMapper<T> rowMapper) throws DataAccessException
·查询数据返回List集合:public <T> java.util.List<T> queryForList(java.lang.String sql, java.lang.Class<T> elementType, @Nullable java.lang.Object... args) throws DataAccessException
·查询单个数据:public <T> java.util.List<T> queryForList(java.lang.String sql, java.lang.Object[] args, java.lang.Class<T> elementType) throws DataAccessException
范例:实现数据的增加
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import java.util.Date;
public class JdbcDemoA {
public static void main(String[] args) throws Exception{
DriverManagerDataSource source =new DriverManagerDataSource();
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setUrl("jdbc:mysql://localhost:3306/springdb");
source.setUsername("root");
source.setPassword("config_mysql");
JdbcTemplate jdbcTemplate = new JdbcTemplate(source);
String sql="Insert Into news(title,pubdata,content) values(?,?,?)" ;
int count = jdbcTemplate.update(sql,"这是新闻标题",new Date(),"这是新闻内容");
System.out.println(count);
source.getConnection().close();
}
}
通过这样一个简单的代码就可以发现,整个程序的处理过程是相当简单的,用户虽然与原始的JDBC没有脱离太多,但是也实现了相同的功能,而且最为重要的是,整个代码操作过程之中,几乎感觉不到PreparedStatement接口的操作,
但是此时的操作只适合于进行数据的简单保存,并不能够取得增长后的数据ID信息,那么要想取得增长后的ID信息要更换更新方法:“update(PreparedStatementCreator psc, KeyHolder generatedKeyHolder) throws DataAccessException;”
如果要更新则必须首先观察PreparedStatementCreator。
public interface PreparedStatementCreator{
public PareparedStatement createPreparedStatement(java.sql.Connection con) throws SQLException
}
这种接口一般都是利用匿名内部类的操作形式完成处理的。
范例:取得增长后的ID数据
package cn.mldn.edu;
import com.mysql.jdbc.Statement;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JdbcDemoB {
public static void main(String[] args) throws Exception{
DriverManagerDataSource source =new DriverManagerDataSource();
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setUrl("jdbc:mysql://localhost:3306/springdb?useSSL=false");
source.setUsername("root");
source.setPassword("config_mysql");
KeyHolder keyHolder = new GeneratedKeyHolder();//取得自动增长列
JdbcTemplate jdbcTemplate = new JdbcTemplate(source);
final String sql="Insert Into news(title,pubdata,content) values(?,?,?)" ;
int count = jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
PreparedStatement pstmt = con.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1,"好好学习1");
pstmt.setDate(2,new java.sql.Date(new java.util.Date().getTime()));
pstmt.setString(3,"天天向上1");
return pstmt;
}
},keyHolder);
System.out.println("更新的行数:"+count+"\t当前id"+ keyHolder.getKey().longValue());
source.getConnection().close();
}
}
如果此时不取得这个增长后的ID,个人觉得还稍微方便一点,但是一旦要取得了这个ID,那么事情就很麻烦。
更新操作之后,下面最重要的就是进行数据查询操作,但是如果要查询数据有一点比较麻烦,就是Spring中并没有支持自动的查询结果与VO的转换,必须要自己完成转换器。
范例:定义一个News.java类
package cn.mldn.vo;
import java.util.Date;
public class News {
private Integer nid;
private String title;
private Date pubdata;
private String content;
}
而随后最为重要的操作就是在查询的处理上,必须使用RowMapper接口转换,这个接口定义如下:
public Interface RowMapper<T>{
public T mapRow(java.sql.ResultSet rs,int rowNum)
throws java.sql.SQLException
}
这里面第一个参数表示数据查询后返回的ResultSet接口对象,利用这个接口对象可以取出查询结构,同时可以取得这次查询返回的当前数据行。
范例:实现查询转换
package cn.mldn.edu;
import cn.mldn.vo.News;
import com.mysql.jdbc.Statement;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class JdbcDemoC {
public static void main(String[] args) throws Exception{
DriverManagerDataSource source =new DriverManagerDataSource();
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setUrl("jdbc:mysql://localhost:3306/springdb?useSSL=false");
source.setUsername("root");
source.setPassword("config_mysql");
JdbcTemplate jdbcTemplate = new JdbcTemplate(source);
String sql="SELECT nid,title,pubdata,content FROM news WHERE title LIKE ? LIMIT ?,?" ;
List<News> all =jdbcTemplate.query(sql, new RowMapper<News>() {
@Override
public News mapRow(ResultSet rs, int rowNum) throws SQLException {
System.out.println("返回的数据行数:"+rowNum);
News vo = new News();
vo.setNid(rs.getInt(1));
vo.setTitle(rs.getString(2));
vo.setPubdata(rs.getDate(3));
vo.setContent(rs.getString(4));
return vo;
}
},"%%",0,2);
System.out.println(all);
source.getConnection().close();
}
}
以上的操作是属于实际开发之中,进行SQL数据与简单Java类转换的标准过程,但是如果现在查询中只有单列数据,则可以使用更为简单的处理过程,例如:queryForList()方法,这个方法可以处理单列,但它不能处理VO转换。
范例:查询一组单列数据
package cn.mldn.edu;
import cn.mldn.vo.News;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class JdbcDemoD {
public static void main(String[] args) throws Exception{
DriverManagerDataSource source =new DriverManagerDataSource();
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setUrl("jdbc:mysql://localhost:3306/springdb?useSSL=false");
source.setUsername("root");
source.setPassword("config_mysql");
JdbcTemplate jdbcTemplate = new JdbcTemplate(source);
String sql="SELECT title FROM news WHERE title LIKE ? LIMIT ?,?" ;
List<String> all =jdbcTemplate.queryForList(sql,new Object[]{"%%",0,2},String.class);
System.out.println(all);
source.getConnection().close();
}
}
但是在实际的编写过程之中还有一种统计数据量的操作也是最为常见的形式。
范例:统计数据量
package cn.mldn.edu;
import cn.mldn.vo.News;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class JdbcDemoD {
public static void main(String[] args) throws Exception{
DriverManagerDataSource source =new DriverManagerDataSource();
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setUrl("jdbc:mysql://localhost:3306/springdb?useSSL=false");
source.setUsername("root");
source.setPassword("config_mysql");
JdbcTemplate jdbcTemplate = new JdbcTemplate(source);
String sql="SELECT COUNT(*) FROM news WHERE title LIKE ? LIMIT ?,?" ;
Integer integer =jdbcTemplate.queryForObject(sql,new Object[]{"%%",0,2},Integer.class);
System.out.println(integer);
source.getConnection().close();
}
}
这个类的基本印象:
·简单操作容易实现,但是一涉及到查询结果与VO的转换,那么这个类支持就变得很麻烦;
·JdbcTemplate时针对于JDBC标准的轻量级封装,所有JDBC的操作在此类中非常清晰;
JdbcTemplate7.4、在Spring配置JDBC操作
如果要想简化整个的JdbcTemplate的操作流程,那么唯一可以进行的方式就是利用配置文件采用依赖注入的方式实现操作。下面也实现相同的操作,但尽量使用更为合理的做法,如果真的在实际开发中使用了JdbcTemplate,那么也只会在数据层上使用。
范例:定义INewsDAO接口
package cn.mldn.dao;
import cn.mldn.vo.News;
public interface INewsDAO {
public boolean doCreate(News vo) throws Exception;
}
package cn.mldn.dao.impl;
import cn.mldn.dao.INewsDAO;
import cn.mldn.vo.News;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
public class NewsDAOImpl implements INewsDAO {
private JdbcTemplate jdbcTemplate;
@Autowired//自动根据匹配类型注入所需要的数据
public NewsDAOImpl(JdbcTemplate jdbcTemplate){
this.jdbcTemplate=jdbcTemplate;
}
@Override
public boolean doCreate(News vo) throws Exception {
String sql="Insert Into news(title,pubdata,content) values(?,?,?)" ;
int count = jdbcTemplate.update(sql,vo.getTitle(),vo.getPubdata(),vo.getContent());
return count > 0;
}
}
package cn.mldn.dao;
import cn.mldn.vo.News;
public interface INewsDAO {
public boolean doCreate(News vo) throws Exception;
}
现在的关键在于applicationContext.xml文件。
范例:配置applicationContext.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<context:component-scan base-package="cn.mldn"/>
<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/springdb"/>
<property name="username" value="root"/>
<property name="password" value="config_mysql"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
那么此时JdbcTemplate类与DatasSource之间的关系就通过配置文件搭配实现完成。
范例:编写测试代码
package cn.mldn.test;
import cn.mldn.dao.INewsDAO;
import cn.mldn.vo.News;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Date;
public class NewsTest {
public static void main(String[] args) throws Exception{
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("cn/applicationContext.xml");
INewsDAO dao = applicationContext.getBean("newsDAOImpl",INewsDAO.class);
News news = new News();
news.setTitle("天气寒冷");
news.setPubdata(new Date(new java.util.Date().getTime()));
news.setContent("请多添衣");
System.out.println(dao.doCreate(news));
}
}
此时所有的功能都通过了Spring进行了管理,那么就表示代码几乎不需要由用户负责处理任何的辅助操作。
7.5、使用C3P0数据库连接池
在所有的实际工作之中,以上使用的“DriverManagerDataSource”的链接类时不会去使用的,因为这种处理操作使用的是传统的JDBC方式处理,就是说它每次操作的时候都需要自己连接数据库,而后还需要关闭数据库,性能太差,所以在实际的工作上我们都会使用数据库连接池来负责数据库连接的管理,但是在JSP的时候学习过Tomcat下配置连接池,只不过这种连接池有一个问题,他必须有Tomcat才可以使用,所以在Spring里面提供有C3P0的连接池组件,利用这个组件就可以实现一个本地Spring容器的数据库连接池操作。
因为在实际的开发之中必须要考虑更换数据库的情况,所以最好的做法是单独建立一个数据库的连接信息文件,这个文件采用属性文件的方式,保存所有与数据库连接有关的信息。
范例:在src下建立一个database.properties文件
db.Driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/springdb?useSSL=false
db.username=root
db.password=config_mysql
pool.max=100
pool.min=1
pool.init=10
pool.maxtime=10
而现在需要在applicationContext.xml文件里面取得这些配置的内容。
范例:修改applicationContext.xml文件,使用C3P0数据库连接池
<!--在本程序之中设置要导入的资源路径,直接通过classpath加载-->
<context:property-placeholder location="classpath:database.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${db.driver}"/> <!--驱动地址-->
<property name="jdbcUrl" value="${db.url}"/> <!--连接地址-->
<property name="user" value="${db.user}"/> <!--用户名-->
<property name="password" value="${db.password}"/> <!--密码-->
<property name="maxPoolSize" value="${pool.max}"/> <!--最大连接数-->
<property name="minPoolSize" value="${pool.min}"/> <!--最小连接数-->
<property name="initialPoolSize" value="${pool.init}"/> <!--初始化连接-->
<property name="maxIdleTime" value="${pool.maxtime}"/> <!--最大等待时间(ms)-->
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
但是需要注意一点,在以后的开发中,只设置一个连接。
7.6、JdbcDaoSupport支持类
虽然可以利用JdbcTemplate进行数据库的操作,但是在大部分的DAO实现子类中很少有人这样做,往往会让DAO即实现子类实现了DAO接口又同时去继承一个JdbcDaoSupport的抽象类。
因为如果不继承,代码就需要编写这样一个功能:
@Autowired//自动根据匹配类型注入所需要的数据
private JdbcTemplate jdbcTemplate;
这些过程应该交给一个统一的类来进行处理,那么久定义了一个JdbcDaoSupport类,但是这个类处理负责处理JdbcTemplate类的对象之外,还可以负责数据库的自动关闭,也就是说如果想让自己的数据库可以自动关闭,就必须继承JdbcaDaoSupport类(import org.springframework.jdbc.core.support.JdbcDaoSupport;)。方法如下:
·设置Jdbc操作模版:
·取得Jdbc操作模版
所有的JdbcTemplate类的对象只有通过getJdbcTemplate()方法取得后才可以正常关闭数据库。
范例:修改NewsDAOImpl子类
@Override
public boolean doCreate(News vo) throws Exception {
String sql="Insert Into news(title,pubdata,content) values(?,?,?)" ;
int count = super.getJdbcTemplate().update(sql,vo.getTitle(),vo.getPubdata(),vo.getContent());
return count > 0;
}
此时的代码操作在增加完成后才可以关闭数据库连接,否则无法关闭。
网友评论