一个简单的jdbc的例子:
#Banzu.class
package com.springframework.sample.jdbc.pojo;
//仅仅是作为简单的java对象(pojo),属性值,构造方法,setget方法
public classBanzu {
private StringbanZu;
private StringfuZeRen;
private StringchengYuan;
public Banzu(){}
public Banzu(StringbanZu,String fuZeRen,StringchengYuan){
this.banZu =banZu;
this.fuZeRen =fuZeRen;
this.chengYuan =chengYuan;
}
public String getBanZu() {
return banZu;
}
public void setBanZu(String banZu) {
this.banZu =banZu;
}
public String getFuZeRen() {
return fuZeRen;
}
public void setFuZeRen(String fuZeRen) {
this.fuZeRen =fuZeRen;
}
public String getChengYuan() {
return chengYuan;
}
public void setChengYuan(String chengYuan) {
this.chengYuan =chengYuan;
}
}
#BanzuRowMapper.class implements RowMapper()
//主要实现Rowmapper接口,主要是对于返回ResultSet集合进行封装得到相应的List<Banzu>集合,这是JdbcTemplate中query(stringsql ,RowMapper)的一个参数,实现对于对象封装转换
package com.springframework.sample.jdbc.pojo;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
public class BanzuRowMapper implementsRowMapper<Banzu> {
@Override
publicBanzu mapRow(ResultSet rs, int rowNum) throws SQLException {
//TODO Auto-generated method stub
Banzubanzu = newBanzu(rs.getString("BANZU_NAME"),rs.getString("FUZEREN"),rs.getString("BANZUCHENGYUAN"));
returnbanzu;
}
}
其中注释写到,RowMapper接口的实现的方法,只要将Resultset中每一row对应到一个对象即可。
#DaoInterface:interface ,提供抽象保存和查询的功能
package com.springframework.sample.jdbc.dao;
import java.util.List;
import com.springframework.sample.jdbc.pojo.Banzu;
public interfaceDaoInterface {
public void saveBanzu(Banzu banzu);
public List<Banzu>selectBanzu(Stringsql);
}
#DaoServiceImpl:class ,implements DaoInterface
//利用JdbcTemplate,同时使用basicDataSource注入,进行操作DataBase
package com.springframework.sample.jdbc.dao;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import com.springframework.sample.jdbc.pojo.Banzu;
import com.springframework.sample.jdbc.pojo.BanzuRowMapper;
public classDaoServiceImpl implements DaoInterface {
private JdbcTemplatejdbcTemplate;
private DataSourcedataSource;
public JdbcTemplategetJdbcTemplate() {
return jdbcTemplate;
}
public voidsetJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate =jdbcTemplate;
}
public voidsetDataSource(DataSource dataSource){
this.jdbcTemplate =new JdbcTemplate(dataSource);
}
@Override
public void saveBanzu(Banzu banzu) {
// TODO Auto-generated method stub
Stringsql= "insert into banzu(BANZU_NAME,FUZEREN,BANZUCHENGYUAN) VALUES (?,?,?)";
if(banzu!=null)
{
this.jdbcTemplate.update(sql,new Object[]{banzu.getBanZu(),banzu.getFuZeRen(),banzu.getChengYuan()});
}
}
@Override
public List<Banzu>selectBanzu(Stringsql){
// TODO Auto-generated method stub
List<Banzu> list = new ArrayList<Banzu>();
if(sql.isEmpty())
sql = "select * from banzu";
list = this.jdbcTemplate.query(sql,new BanzuRowMapper());
return list;
}
}
这里有个疑问,虽然是可以注入,但是jdbcTemplate是是在哪里注入的那?这里虽然是将dataSource进行了set注入,同时也new了一个JdbcTemplate的对象,但是它应该先调用jdbcTemplate及其的注入,但是实际上我们并没有看到?难道是加载spring-config.xml其中dataSource初始化了它,进行set它的时候,创建的,但是需要jdbcTemplate时,确实是没有找到相应的注入,难道原因就是这个new,还是spring框架中其他地方有注入?
#ServiceSample1:class service logical ,mainmethod to run
package com.springframework.sample.jdbc.service;
import java.util.List;
importorg.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
importcom.springframework.sample.jdbc.dao.DaoInterface;
importcom.springframework.sample.jdbc.pojo.Banzu;
//执行添加表的数据和查询表中数据,并显示出来
public class ServiceSample1 {
privateDaoInterface daoIntBanzu;
privateBanzu banZu;
publicDaoInterface getDaoIntBanzu() {
returndaoIntBanzu;
}
publicvoid setDaoIntBanzu(DaoInterface daoIntBanzu) {
this.daoIntBanzu= daoIntBanzu;
}
publicBanzu getBanZu() {
returnbanZu;
}
publicvoid setBanZu(Banzu banZu) {
this.banZu= banZu;
}
publicvoid createBanzu(Banzu banzu){
daoIntBanzu.saveBanzu(banzu);
}
publicList<Banzu> getBanzu(String sql){
returndaoIntBanzu.selectBanzu(sql);
}
public static void main(String []args){
//利用applicationcontext接口获得相应配置文件中的信息
ApplicationContextcontext = new ClassPathXmlApplicationContext("spring-config.xml");
//初始化一个新的班组,然后加入数据库
Banzubanzu = new Banzu();
banzu.setBanZu("ban"+System.currentTimeMillis());
banzu.setChengYuan("du1,zhao2,long3");
banzu.setFuZeRen("dudu");
//通过getBean获取相应注入对象(springIOC容器管理初始化和销毁)
ServiceSample1serv =(ServiceSample1) context.getBean("service1");
serv.createBanzu(banzu);
List<Banzu>list = serv.getBanzu("select * from banzu");
for(inti=0;i<list.size();i++){
Banzutemp_banzu = list.get(i);
System.out.println("banzuname:" +temp_banzu.getBanZu()+",fuzeren:"+temp_banzu.getFuZeRen()+",chengyuan:"+temp_banzu.getChengYuan());
}
}
}
#spring-config.xml 放置在src目录下即可
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<beanid="dataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">
<propertyname="driverClassName" value ="oracle.jdbc.driver.OracleDriver"/>
<propertyname="url"value ="jdbc:oracle:thin:@localhost:1521:orcl"/>
<propertyname= "username"value ="scott" />
<propertyname= "password"value ="adu636"/>
</bean>
<beanid= "jdbcTemplate"class ="com.springframework.sample.jdbc.dao.DaoServiceImpl">
<propertyname="dataSource"ref ="dataSource"/>
</bean>
<beanid="service1"class="com.springframework.sample.jdbc.service.ServiceSample1">
<propertyname="daoIntBanzu"ref ="jdbcTemplate"/>
</bean>
</beans>
//这是使用的oracle10g,数据库驱动则是classes12.jar,ojdbc14.jar,
//spring 3.2.4把其所有的jar包都加入了其中,spring还需要日志记录commons-logging.jar,另外还需要commons-dbcp.jar(是用来BasicDataSource),及其BasicDataSource配套的commons-pool.jar,(数据库连接池)
//同时这里使用的是JDK7的版本,本身系统为win8,JDK也是64bit的。
其他题的过程:ServiceSample1:main()方法被spring-config.xml中配置完成初始化,----》其中需要依赖注入相应的DaoServiceImpl(daoIntBanzu:DaoInterface)----》而DaoServiceImpl则需要注入相应的dataSource:BasicDataSource,完成数据库相应访问的配置,并利用jdbcTemplate中的数据库操作方法读写数据库。----》一系列的注入完成后,开始添加Banzu和查询该表中对象数据。-----
这里的一个新的知识点就是使用了RowMapper接口,实现了该接口其中的方法。
一般的jdbc操作时:
Class.forName(“加载相应的驱动”);
Connectioncon = DriverManager.getConnection(url,username,password);
Statementstat = con.createStatement();
ResultSetrs = stat.executeQuery(sql);
while(rs.next())
{
Stringname = rs.getString(“name”);…
}
If(rs!=null)
{
rs.close();
rs = null;
}
If(stat!=null){
Stat.close();
Stat = null;
}
If(con!=null){
Con.close();
Con = null;
}
这里没有加入异常。
8.1这里我们分析spring的jdbc中的实现
通过前面的例子,同时结合JdbcTemplate的定义中我们可以看出
JDBCTEMPLATE是spring中jdbc core包中的核心类,它简化了jdbc的使用,同时可以避免一些普通的错误。它执行jdbc的工作流,留给应用程序输入sql参数,同时将运行结果result提取作为输出。jdbcTemplate可以执行sql查询或者update,初始化resultset上面的迭代器,并且捕获jdbcexception.
使用该类需要实现回调接口,进行相关清晰的定义。preparedStatementCreator callbackinterface ,通过connection创建一个PreparedStatement。而ResultSetExtractor接口会从resultset中提出相关数据。可以参考实现PreparedStatementSetter和RowMapper接口,前者参数化sql语句预处理防止sql注入,后者RowMapper的接口可以实现方法获得ResultSet中每一行row数据转化为相应的对象实例。
JdbcTemplate的实例化,可以
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
也就是说可以自己设置相应的数据源,比如BasicDataSource,C3P0等。然后再xml文件中配置一下,然后有spring进行解析注入。
jdbcTemplate类实现了JdbcAccessor抽象类,实现了JdbcOperation接口,jdbcaccessor类主要是完成了对于dataSource的set方法,延迟加载设置默认为true,sql异常转换,logger记录debug级的信息。
Jdbcoperator接口定义的类无外乎query,update等方法,主要是针对于statement,preparedstatement,callbackstatement类别的各种query、update方法。
如果是常亮的话,可以设置为:
Private static final type CAPS_NAME = “”;//大写名称
Static final type CAPS_NAME = “”; //接口中常亮
Jdbctemplate中重要的方法介绍:
(1) 设置数据源的构造方法
Public JdbcTemplate(DataSource dataSource){
setDataSource(dataSource);
afterPropertiesSet();//所有参数设置后执行
}
(2)通过阅读spring源码,可以了解到一半的比如处理每个业务的如之前的getBean(),方法一半都是处理之前做一些准备,真正的处理则是类似于doGetBean()这样的方法处理的。如下是jdbcTemplate对于connection的处理,利用了DataSourceUtils工具类进行处理
public <T> T execute(ConnectionCallback<T>action)throwsDataAccessException {
Assert.notNull(action,"Callback object must not be null");
//(1)DataSourceUtils工具类通过数据源获得connection
Connectioncon= DataSourceUtils.getConnection(getDataSource());
try {
ConnectionconToUse= con;
if (this.nativeJdbcExtractor!=null){
//(2)抽取本地jdbc连接,可以转换成oracle等其他的连接
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
else {
//(3)创建连接代理, also preparingreturned Statements.
conToUse =createConnectionProxy(con);
}
//(5)这里需要通过connection进行相应的查询获得相应结果
returnaction.doInConnection(conToUse);
}
catch (SQLExceptionex) {
//释放 Connectionearly,避免潜在的连接池死锁
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throwgetExceptionTranslator().translate("ConnectionCallback",getSql(action), ex);
}
finally {
//(4)释放 Connection early,避免潜在的连接池死锁
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
中间(1)DataSourceUtils.getConnection(dataSource);
其中真正获得connection的地方在:doGetConnection()中:
public staticConnection doGetConnection(DataSource dataSource) throwsSQLException {
Assert.notNull(dataSource,"No DataSource specified");
//a从事务管理器中获得connection一个中间存放的列表holder,从其中取
ConnectionHolderconHolder= (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder !=null && (conHolder.hasConnection() ||conHolder.isSynchronizedWithTransaction())){
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection fromDataSource");
//如果holder中没有connection,则从dataSource中取,因为dataSource中一般我们会设置最小和最大的连接数
conHolder.setConnection(dataSource.getConnection());
}
returnconHolder.getConnection();
}
// c如果holder中没有,且dataSource中也没有可以使用的,可以新创建,因为可能空闲时保有的connection数量较少,可以创建添加到pool中
logger.debug("Fetching JDBC Connection from DataSource");
Connectioncon= dataSource.getConnection();
//是否需要同步处理
if(TransactionSynchronizationManager.isSynchronizationActive()) {
logger.debug("Registering transaction synchronization for JDBCConnection");
ConnectionHolderholderToUse=conHolder;
if (holderToUse ==null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
newConnectionSynchronization(holderToUse,dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse !=conHolder) {
TransactionSynchronizationManager.bindResource(dataSource,holderToUse);
}
}
return con;
}
(2) 抽取本地jdbc连接,可以转换成oracle等其他的连接
他可以获得如图所示的相关的extractor,如c3p0,commonsDbcp,JBoss等
(3)或者是创建连接代理, also preparingreturned Statements.
conToUse = createConnectionProxy(con);
这一块可以参考相应的反射和代理,机器对应的模式
(4)realease释放connection
doReleaseConnection(con,dataSource);
public staticvoiddoReleaseConnection(Connectioncon,DataSourcedataSource)throwsSQLException {
if (con ==null) {
return;
}//先从数据源中,获得connectionholder比较是否是该con,是的话released
if (dataSource !=null) {
ConnectionHolderconHolder= (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder !=null && connectionEquals(conHolder,con)) {
// 这里只是将连接数减1,并没有关闭,仍在pool中.
conHolder.released();
return;
}
}
logger.debug("Returning JDBC Connection to DataSource");
//没有数据源的话直接close
doCloseConnection(con,dataSource);
}
public voidreleased() {
this.referenceCount--;
}
这里是先判断是否有数据源,有的话利用connectionHolder关闭;而且这里的关闭并不是真正意义上的关闭,并没有调用如下的close方法。调用doCloseConnection()只是处理一下ConnectionHolder,将使用的资源数减1,真正的connection仍然存在,它由相关的数据源进行处理。
public staticvoidcloseConnection(Connectioncon){
if (con !=null) {
try {
con.close();
}
catch (SQLExceptionex) {
logger.debug("Could not close JDBC Connection",ex);
}
catch (Throwableex) {
// We don't trust the JDBC driver: It might throwRuntimeException or Error.
logger.debug("Unexpected exception on closing JDBCConnection",ex);
}
}
}
public voidreleased() {
super.released();
if (!isOpen() &&this.currentConnection != null) {
//这里release主要是执行了前面类似的内容
this.connectionHandle.releaseConnection(this.currentConnection);
this.currentConnection =null;
}
}
这样就可以看出和最初学习jdbc而没有框架的时候的释放关闭资源没有什么本质上的区别,connection真正释放的过程是一样的,只是这里因为业务和上升到框架角度需要考虑的额外内容、处理的额外事情多了一些。而且其中涉及到事务相应的内容,这暂且不讲。
讲了这么多这里还有一个ConnectionCallBack是什么东西?
找到其定义:泛化的回调接口,用来操作jdbcconnection的。可以使用任意一种类型和数量的statement,并且执行任意数量的操作在单独的connection上。
光看一个使用它作为参数的我们可能不了解,但是通过如下代码:
getJdbcTemplate().execute(newConnectionCallback<Object>() {
public ObjectdoInConnection(Connectioncon)throwsSQLException, DataAccessException {
// 只是执行插入操作
PreparedStatementps =null;
try {
ps = con.prepareStatement(getInsertString());
setParameterValues(ps,values, getInsertTypes());
ps.executeUpdate();
}
finally {
JdbcUtils.closeStatement(ps);
}
//Get the key查询
StatementkeyStmt=null;
ResultSetrs =null;
Map<String, Object> keys = new HashMap<String,Object>(1);
try {
keyStmt =con.createStatement();
rs = keyStmt.executeQuery(keyQuery);
if (rs.next()) {
longkey = rs.getLong(1);
keys.put(getGeneratedKeyNames()[0],key);
keyHolder.getKeyList().add(keys);
}
}
finally {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(keyStmt);
}
returnnull;
}
});
}
(5)//(5)这里需要通过connection进行相应的查询获得相应结果
returnaction.doInConnection(conToUse);
DoInConnection中如(4)中所写的,那样connectionCallBack主要是操作connection,可以看出这里通过con获得了statement,然后进一步进行处理
(3) jdbcTemplate方法中execute方法(StatementCallBack)
statementCallBack:Interface,其功能和ConnectionCallBack接口类似,实现的接口主要是对于statement进行相关的操作
public <T> T execute(StatementCallback<T>action) throws DataAccessException {
Assert.notNull(action,"Callback object must not be null");
Connectioncon= DataSourceUtils.getConnection(getDataSource());
Statementstmt= null;
try {
ConnectionconToUse= con;
if (this.nativeJdbcExtractor!=null&&
this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()){
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
stmt = conToUse.createStatement();
applyStatementSettings(stmt);
StatementstmtToUse= stmt;
if (this.nativeJdbcExtractor!=null){
stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
}
Tresult= action.doInStatement(stmtToUse);
handleWarnings(stmt);
returnresult;
}
catch (SQLExceptionex) {
// Release Connection early, to avoid potentialconnection pool deadlock
// in the case when the exception translator hasn't beeninitialized yet.
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throwgetExceptionTranslator().translate("StatementCallback",getSql(action), ex);
}
finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
如果分析过ConnectionCallBack后则很容易理解,Tresult= action.doInStatement(stmtToUse);
通过如下代码:这里是内部类ExceuteStatementCallBack,实现了StatementCallBack接口,实现了其中DoInStatement方法,这种方式需要注意一下,在方法内部定义了ExecuteStatementCallBack类,实现了DoInStatement()方法,最后执行了execute(StatementCallBack),执行了DoInStatement()方法。
public voidexecute(finalStringsql)throwsDataAccessException {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL statement [" +sql+ "]");
}
classExecuteStatementCallbackimplements StatementCallback<Object>, SqlProvider {
public ObjectdoInStatement(Statement stmt) throws SQLException {
stmt.execute(sql);
return null;
}
public String getSql() {
returnsql;
}
}
execute(newExecuteStatementCallback());
}
阅读代码我们可以看到好多的新东西,如asseert.notNull(sql,”xxxis not null”);
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL query ["+sql+ "]");
}
Assert 是断言语句可以用于测试如JUnit,而logger日志记录可以给我们提供一些信息提示。这对于开发和调试,测试等都是很重的工具。
(4) jdbcTemplate 的方法有些地方我们看到如下代码
ResultSetExtractor rse ;returnrse.extractData(rsToUse);
它是结合了相应的数据库的驱动中对于ResultSet的处理的代码,同时结合了spring框架中的结果集进行了冲新的封装。形成相应的rowset,类似于resultset的一行数据,放置到一行rowset,每一行又分为若干个column,存放一个个的属性数据,可以进一步研究。
(5) jdbcTemplate方法 批处理
这里讲解一个:public int[] batchUpdate(finalString[] sql)throwsDataAccessException {
Assert.notEmpty(sql,"SQL array must not be empty");
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL batch update of " +sql.length +" statements");
}
//这里也是方法内部定义了一个实现相应接口的类,实现了其中重要的方法,doInStatement()方法
class BatchUpdateStatementCallbackimplementsStatementCallback<int[]>, SqlProvider {
privateString currSql;
public int[]doInStatement(Statement stmt)throwsSQLException, DataAccessException {
int[]rowsAffected = newint[sql.length];
if (JdbcUtils.supportsBatchUpdates(stmt.getConnection())) {
for (StringsqlStmt : sql) {
this.currSql =sqlStmt;
stmt.addBatch(sqlStmt);
}
rowsAffected =stmt.executeBatch();
}
else {
for (inti = 0; i < sql.length; i++) {
this.currSql =sql[i];
if (!stmt.execute(sql[i])) {
rowsAffected[i] =stmt.getUpdateCount();
}
else {
thrownewInvalidDataAccessApiUsageException("Invalidbatch SQL statement: " +sql[i]);
}
}
}
returnrowsAffected;
}
publicString getSql() {
return this.currSql;
}
}
return execute(newBatchUpdateStatementCallback());
}
(6)PreparedStatementCreator顾名思义就是用来创建相应statement的,它是一个接口,如下面的实现 public PreparedStatementcreatePreparedStatement(Connectioncon) throwsSQLException方法
private staticclassSimplePreparedStatementCreatorimplements PreparedStatementCreator, SqlProvider {
private final String sql;
public SimplePreparedStatementCreator(String sql) {
Assert.notNull(sql,"SQL must not be null");
this.sql =sql;
}
public PreparedStatementcreatePreparedStatement(Connectioncon) throwsSQLException {
returncon.prepareStatement(this.sql);
}
public String getSql() {
return this.sql;
}
他的操作也和上面相类似,一般就是在方法内部声明该类,实现其中的方法,然后进行下一步的执行调用。
另外:preparedStatement继承自statement,但是有何它有些不同之处:
(a) preparedStatement实例中包含已经编译的sql语句,其sql可以包含一个或者是多个in参数,他们在创建时未被指定,只是一?作为占位符,但是在执行之前必须为其指定相应的数据,可以通过setXXX方法提供数据。
(b)由于preparedstatement对象已经预编译过,其执行的速度要快于statement对象。因此多次执行的sql语句经常创建为preparedstatement对象,提高效率。