- 我们看到,在我们自定义的数据源中,主要有这么几个变量:
初始化连接数,最大连接数,当前的连接数,连接池(因为我们可能需要频繁的添加连接和删除连接所以使用LinkedList,因为这个list是链表结构的,增加和删除效率高)
主要流程是:初始化数据源的时候,初始化一定量的连接放到池子中,当用户使用getConnection()方法取出连接的时候,我们会判断这个连接池中还有没有连接了,有就直接取出第一个连接返回,没有的话,我们在判断当前的连接数有没有超过最大连接数,超过的话,就抛出一个异常(其实这里还可以选择等待其他连接的释放,这个具体实现是很麻烦的),没有超过的话,就创建连接,并且将其放入池子中。
我们自定义的数据源是实现了JDBC中的DataSource接口的,这个接口很重要的,后面我们会说到apache的数据源都是要实现这个接口的,这个接口统一了数据源的标准,这个接口中有很多实现的,所以看到我们的数据源类中有很多没必要的方法,但是那个方法都是要实现的,最重要的就是要实现getConnection方法,其他的实现都只需要调用super.XXX就可以了。
在JdbcUtils类中我们也是需要修改的,首先我们要在静态代码块中初始化我们的数据源,在getConnection方法中调用数据源的getConnection方法,在free方法中调用数据源的free方法即可。
看一下测试类:
- package com.weijia.datasource;
- import java.sql.Connection;
- import com.weijia.firstdemo.JdbcUtils;
- public class Test {
- public static void main(String[] args) throws Exception{
- for(int i=0;i<10;i++){
- Connection conn = JdbcUtils.getConnection();
- System.out.println(conn);
- JdbcUtils.free(null, null, conn);
- }
- }
- }
- package com.weijia.datasource;
- import java.sql.Connection;
- import com.weijia.firstdemo.JdbcUtils;
- public class Test {
- public static void main(String[] args) throws Exception{
- for(int i=0;i<10;i++){
- Connection conn = JdbcUtils.getConnection();
- System.out.println(conn);
- JdbcUtils.free(null, null, conn);
- }
- }
- }
运行结果:
我们可以看到,我们在测试代码中申请了10个连接,从结果上可以看出前五个是不同的连接,后五个连接和前五个是一样的,这是因为我们在释放连接的时候就是free方法中,是将连接重新放到池子中的,上面显示的是五个,是因为我们初始化的连接数是5个,当第一个连接释放的时候这个连接其实已经放到了池子的第六个位置,以此类推。
下面我们继续来看下个问题,我们在上面的数据源中可以看到,我们定义了一个free方法来释放连接的,然后在JdbcUtils中调用这个方法即可,但是这个貌似不太符合我们的使用习惯,因为之前我们看到我们释放连接的时候都是使用close方法的,所以这里面我们在修改一下,至于怎么修改呢?
首先我们知道那个close方法是JDBC中的Connection接口中的,所有自定义的连接都是需要实现这个接口的,那么我们如果我们想让我们free中的逻辑放到close中的话,就需要实现这个接口了,我们可以看到
-
-
- DriverManager.getConnection(url)
- DriverManager.getConnection(url)
通过这种方式获取到的Connection也是mysql中实现了Connection的接口的,那么现在我们可能需要自定一个我们自己的连接,然后实现Connection接口,将free方法中的逻辑搬到close方法中,同时我们还要在连接类中保持一个mysql中的连接对象,这里面的逻辑有点不好理解,先看代码:
- package com.weijia.datasource;
- import java.sql.CallableStatement;
- import java.sql.Connection;
- import java.sql.DatabaseMetaData;
- import java.sql.PreparedStatement;
- import java.sql.SQLException;
- import java.sql.SQLWarning;
- import java.sql.Savepoint;
- import java.sql.Statement;
- import java.util.Map;
- public class MyConnection implements Connection{
- //组合方式:静态代理
- private Connection realConnection;
- private MyDataSource2 dataSource;
- //当前连接的使用的次数
- private int maxUseCount = 5;
- private int currentUseCount = 0;
- public MyConnection(Connection conn,MyDataSource2 dataSource){
- this.realConnection = conn;
- this.dataSource = dataSource;
- }
- public void close() throws SQLException {
- this.currentUseCount++;
- if(this.currentUseCount < this.maxUseCount){
- this.dataSource.free(this);
- }else{
- dataSource.currentCount–;
- this.realConnection.close();
- }
- }
- public void clearWarnings() throws SQLException {
- this.realConnection.clearWarnings();
- }
- public void commit() throws SQLException {
- this.realConnection.commit();
- }
- public Statement createStatement() throws SQLException {
- return this.realConnection.createStatement();
- }
- public Statement createStatement(int resultSetType, int resultSetConcurrency)throws SQLException {
- return this.realConnection.createStatement(resultSetType, resultSetConcurrency);
- }
- public Statement createStatement(int resultSetType,int resultSetConcurrency, int resultSetHoldability)throws SQLException {
- return this.realConnection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
- }
- public boolean getAutoCommit() throws SQLException {
- return this.realConnection.getAutoCommit();
- }
- public String getCatalog() throws SQLException {
- return this.realConnection.getCatalog();
- }
- public int getHoldability() throws SQLException {
- return this.realConnection.getHoldability();
- }
- public DatabaseMetaData getMetaData() throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public int getTransactionIsolation() throws SQLException {
- // TODO Auto-generated method stub
- return 0;
- }
- public Map<String, Class<?>> getTypeMap() throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public SQLWarning getWarnings() throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public boolean isClosed() throws SQLException {
- // TODO Auto-generated method stub
- return false;
- }
- public boolean isReadOnly() throws SQLException {
- // TODO Auto-generated method stub
- return false;
- }
- public String nativeSQL(String sql) throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public CallableStatement prepareCall(String sql) throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public CallableStatement prepareCall(String sql, int resultSetType,
- int resultSetConcurrency) throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public CallableStatement prepareCall(String sql, int resultSetType,
- int resultSetConcurrency, int resultSetHoldability)
- throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public PreparedStatement prepareStatement(String sql) throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
- throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
- throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public PreparedStatement prepareStatement(String sql, String[] columnNames)
- throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public PreparedStatement prepareStatement(String sql, int resultSetType,
- int resultSetConcurrency) throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public PreparedStatement prepareStatement(String sql, int resultSetType,
- int resultSetConcurrency, int resultSetHoldability)
- throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public void releaseSavepoint(Savepoint savepoint) throws SQLException {
- // TODO Auto-generated method stub
- }
- public void rollback() throws SQLException {
- // TODO Auto-generated method stub
- }
- public void rollback(Savepoint savepoint) throws SQLException {
- // TODO Auto-generated method stub
- }
- public void setAutoCommit(boolean autoCommit) throws SQLException {
- // TODO Auto-generated method stub
- }
- public void setCatalog(String catalog) throws SQLException {
- // TODO Auto-generated method stub
- }
- public void setHoldability(int holdability) throws SQLException {
- // TODO Auto-generated method stub
- }
- public void setReadOnly(boolean readOnly) throws SQLException {
- // TODO Auto-generated method stub
- }
- public Savepoint setSavepoint() throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public Savepoint setSavepoint(String name) throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public void setTransactionIsolation(int level) throws SQLException {
- // TODO Auto-generated method stub
- }
- public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
- // TODO Auto-generated method stub
- }
- }
- package com.weijia.datasource;
- import java.sql.CallableStatement;
- import java.sql.Connection;
- import java.sql.DatabaseMetaData;
- import java.sql.PreparedStatement;
- import java.sql.SQLException;
- import java.sql.SQLWarning;
- import java.sql.Savepoint;
- import java.sql.Statement;
- import java.util.Map;
- public class MyConnection implements Connection{
- //组合方式:静态代理
- private Connection realConnection;
- private MyDataSource2 dataSource;
- //当前连接的使用的次数
- private int maxUseCount = 5;
- private int currentUseCount = 0;
- public MyConnection(Connection conn,MyDataSource2 dataSource){
- this.realConnection = conn;
- this.dataSource = dataSource;
- }
- public void close() throws SQLException {
- this.currentUseCount++;
- if(this.currentUseCount < this.maxUseCount){
- this.dataSource.free(this);
- }else{
- dataSource.currentCount--;
- this.realConnection.close();
- }
- }
- public void clearWarnings() throws SQLException {
- this.realConnection.clearWarnings();
- }
- public void commit() throws SQLException {
- this.realConnection.commit();
- }
- public Statement createStatement() throws SQLException {
- return this.realConnection.createStatement();
- }
- public Statement createStatement(int resultSetType, int resultSetConcurrency)throws SQLException {
- return this.realConnection.createStatement(resultSetType, resultSetConcurrency);
- }
- public Statement createStatement(int resultSetType,int resultSetConcurrency, int resultSetHoldability)throws SQLException {
- return this.realConnection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
- }
- public boolean getAutoCommit() throws SQLException {
- return this.realConnection.getAutoCommit();
- }
- public String getCatalog() throws SQLException {
- return this.realConnection.getCatalog();
- }
- public int getHoldability() throws SQLException {
- return this.realConnection.getHoldability();
- }
- public DatabaseMetaData getMetaData() throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public int getTransactionIsolation() throws SQLException {
- // TODO Auto-generated method stub
- return 0;
- }
- public Map<String, Class<?>> getTypeMap() throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public SQLWarning getWarnings() throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public boolean isClosed() throws SQLException {
- // TODO Auto-generated method stub
- return false;
- }
- public boolean isReadOnly() throws SQLException {
- // TODO Auto-generated method stub
- return false;
- }
- public String nativeSQL(String sql) throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public CallableStatement prepareCall(String sql) throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public CallableStatement prepareCall(String sql, int resultSetType,
- int resultSetConcurrency) throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public CallableStatement prepareCall(String sql, int resultSetType,
- int resultSetConcurrency, int resultSetHoldability)
- throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public PreparedStatement prepareStatement(String sql) throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
- throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
- throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public PreparedStatement prepareStatement(String sql, String[] columnNames)
- throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public PreparedStatement prepareStatement(String sql, int resultSetType,
- int resultSetConcurrency) throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public PreparedStatement prepareStatement(String sql, int resultSetType,
- int resultSetConcurrency, int resultSetHoldability)
- throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public void releaseSavepoint(Savepoint savepoint) throws SQLException {
- // TODO Auto-generated method stub
- }
- public void rollback() throws SQLException {
- // TODO Auto-generated method stub
- }
- public void rollback(Savepoint savepoint) throws SQLException {
- // TODO Auto-generated method stub
- }
- public void setAutoCommit(boolean autoCommit) throws SQLException {
- // TODO Auto-generated method stub
- }
- public void setCatalog(String catalog) throws SQLException {
- // TODO Auto-generated method stub
- }
- public void setHoldability(int holdability) throws SQLException {
- // TODO Auto-generated method stub
- }
- public void setReadOnly(boolean readOnly) throws SQLException {
- // TODO Auto-generated method stub
- }
- public Savepoint setSavepoint() throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public Savepoint setSavepoint(String name) throws SQLException {
- // TODO Auto-generated method stub
- return null;
- }
- public void setTransactionIsolation(int level) throws SQLException {
- // TODO Auto-generated method stub
- }
- public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
- // TODO Auto-generated method stub
- }
- }
首先看到了这个类中有很多恶心的代码,那些方法都是Connection接口中的,我们这里只需要实现close方法就可以了,其他的方法中可以添加:
我们看到会在类中保留一个Connection对象,这个对象就是真实的连接对象,即我们使用
我们看一下close方法吧:
- public void close() throws SQLException {
- this.currentUseCount++;
- if(this.currentUseCount < this.maxUseCount){
- this.dataSource.free(this);
- }else{
- dataSource.currentCount–;
- this.realConnection.close();
- }
- }
- public void close() throws SQLException {
- this.currentUseCount++;
- if(this.currentUseCount < this.maxUseCount){
- this.dataSource.free(this);
- }else{
- dataSource.currentCount--;
- this.realConnection.close();
- }
- }
首先当用户调用close方法的时候当前连接的使用数就加一,这里有些同学可能不能理解,我们想想上面还记得我们释放连接的时候是怎么做的,是将这个连接重新放到池子中,所以这个连接又被用了一次,所以这里面是加一,当这个连接的当前使用次数没有超过他的最大使用次数的话,就还把他放到池子中(就是数据源中的free方法,这个方法中传递的参数是我们自定义的连接对象,因为我们不是真的需要关闭这个连接的),如果使用次数超过了最大使用次数的话,我们就将这个连接真正的释放关闭了,同时需要将数据源中当前的连接数减去一,这里我们是调用真实连接的关闭方法的,所以我们需要在我们自定义的连接中保持一个真实连接的对象,其实我们采用的是组合的方法,在一个要想调用另外类中的方法,我们需要在本类中维持一个他的对象,然后进行调用他特定的方法,这种方式也是一种设计模式叫做:静态代理,相当于我们本类是另外一个类的代理。
同时我们需要在构造函数中传递一个数据源对象进来的,当然我们这时候需要在之前的数据源中修改一下,这里修改很简单的,只需要修改数据源中的createConnection方法就可以了:
- private Connection createConnection() throws SQLException{
- Connection realConn = DriverManager.getConnection(url);
- MyConnection myConnection = new MyConnection(realConn,this);
- return myConnection;
- }
- private Connection createConnection() throws SQLException{
- Connection realConn = DriverManager.getConnection(url);
- MyConnection myConnection = new MyConnection(realConn,this);
- return myConnection;
- }
我们返回的其实是我们自己的定义的连接,这个连接其实也是真实连接的一个代理对象。这样我们在JdbcUtils中的free方法中直接调用:
而不需要调用:
这样的释放方式就和我们之前普通连接的释放方式是一样的。其实我们上面做的这么多的操作就是为了这个,想让用户能够还是直接调用conn.close方法就可以释放连接,我们还是运行一下之前的测试类:
- package com.weijia.datasource;
- import java.sql.Connection;
- import com.weijia.firstdemo.JdbcUtils;
- public class Test {
- public static void main(String[] args) throws Exception{
- for(int i=0;i<10;i++){
- Connection conn = JdbcUtils.getConnection();
- System.out.println(conn);
- JdbcUtils.free(null, null, conn);
- }
- }
- }
- package com.weijia.datasource;
- import java.sql.Connection;
- import com.weijia.firstdemo.JdbcUtils;
- public class Test {
- public static void main(String[] args) throws Exception{
- for(int i=0;i<10;i++){
- Connection conn = JdbcUtils.getConnection();
- System.out.println(conn);
- JdbcUtils.free(null, null, conn);
- }
- }
- }
运行结果如下:
我们看到前五个用的是同一个连接对象,这个原因就是我们在我们自定义的连接MyConnection类中使用了当前连接的最大使用次数是5次
我们看到在定义我们自己的连接类的时候,需要实现Connection接口,这个接口中需要实现的方法很多,其实我们只需要一个close方法就可以了,这时候我们还可以将我们的代码在修改一下,下面是我们修改之后的自定义连接类:
- package com.weijia.datasource;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import java.sql.Connection;
- public class MyConnectionHandler implements InvocationHandler{
- private Connection realConnection = null;
- private Connection warpedConnection = null;
- private MyDataSource2 dataSource = null;
- //当前连接的使用的次数
- private int maxUseCount = 5;
- private int currentUseCount = 0;
- public MyConnectionHandler(MyDataSource2 dataSource){
- this.dataSource = dataSource;
- }
- public Connection bind(Connection conn){
- this.realConnection = conn;
- warpedConnection = (Connection)Proxy.newProxyInstance(this.getClass().getClassLoader(),new Class[]{Connection.class},this);
- return warpedConnection;
- }
- public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
- if(“close”.equals(method.getName())){
- this.currentUseCount++;
- if(this.currentUseCount < this.maxUseCount){
- this.dataSource.free(warpedConnection);
- }else{
- dataSource.currentCount–;
- this.realConnection.close();
- }
- }
- return method.invoke(this.realConnection, args);
- }
- }
- package com.weijia.datasource;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import java.sql.Connection;
- public class MyConnectionHandler implements InvocationHandler{
- private Connection realConnection = null;
- private Connection warpedConnection = null;
- private MyDataSource2 dataSource = null;
- //当前连接的使用的次数
- private int maxUseCount = 5;
- private int currentUseCount = 0;
- public MyConnectionHandler(MyDataSource2 dataSource){
- this.dataSource = dataSource;
- }
- public Connection bind(Connection conn){
- this.realConnection = conn;
- warpedConnection = (Connection)Proxy.newProxyInstance(this.getClass().getClassLoader(),new Class[]{Connection.class},this);
- return warpedConnection;
- }
- public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
- if("close".equals(method.getName())){
- this.currentUseCount++;
- if(this.currentUseCount < this.maxUseCount){
- this.dataSource.free(warpedConnection);
- }else{
- dataSource.currentCount--;
- this.realConnection.close();
- }
- }
- return method.invoke(this.realConnection, args);
- }
- }
第一个:需要代理对象的类加载器
第二个:需要代理对象实现的接口
第三个:InvocationHandler回调接口,我们主要的工具都是实现这个接口中的invoke方法
然后我们在invoke方法中拦截close方法即可,将之前的close方法中的逻辑搬到这里就可以了。我们使用上面的测试代码运行如下:
这里我们就看到了使用动态代理很简单的,但是有一个限制,就是代理对象必须要实现一个接口,这里正好是Connection接口,他比静态代理优雅了很多的,后面我们在说到spring的时候还会说到这个动态代理模式的
好了,上面我们就可以看到我们自己定义了一个数据源,连接,这样对我们后面的操作优化了很多。
下面我们在来看一下apache的数据源DataSource,其实这个数据源大体上和我们上面设计的以一样的,只是他做了更优化,更好。
首先我们导入需要的jar包:
然后我们定义一个dbcpconfig.properties文件,用于配置数据源的相关信息:
- #连接设置
- driverClassName=com.mysql.jdbc.Driver
- url=jdbc:mysql://localhost:3306/test
- username=root
- password=123456
- #<!– 初始化连接 –>
- initialSize=10
- #最大连接数量
- maxActive=50
- #<!– 最大空闲连接 –>
- maxIdle=20
- #<!– 最小空闲连接 –>
- minIdle=5
- #<!– 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 –>
- maxWait=60000
- #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
- #注意:”user” 与 “password” 两个属性会被明确地传递,因此这里不需要包含他们。
- connectionProperties=useUnicode=true;characterEncoding=gbk;generateSimpleParameterMetadata=true
- #指定由连接池所创建的连接的自动提交(auto-commit)状态。
- defaultAutoCommit=true
- #driver default 指定由连接池所创建的连接的只读(read-only)状态。
- #如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
- defaultReadOnly=
- #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
- #可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
- defaultTransactionIsolation=READ_UNCOMMITTED
- 从这些配置上我们可以看到前面的几个参数的含义就是我们自定义数据源中的使用到的,这里还有一个参数是maxWait是超时,这个就是我们在获取连接的时候,当连接数超过最大连接数的时候,需要等待的时间,在前面我们自己定义的数据源中我们是采用抛出异常的问题来解决的,这里我们看到apache是采用线程等待的方式来解决的。
- 我们在代码里面修改的东西也是很少的,在JdbcUtils中的静态代码块中使用apache的数据源即可:
- //使用Apache的DBCP数据源
- Properties prop = new Properties();
- prop.load(JdbcUtils.class.getClassLoader().getResourceAsStream(“dbcpconfig.properties”));
- dataSource = BasicDataSourceFactory.createDataSource(prop);
- #连接设置 <br>
- driverClassName=com.mysql.jdbc.Driver <br>
- url=jdbc:mysql://localhost:3306/test <br>
- username=root <br>
- password=123456
JDBC中CRUD的模板模式
我们从前面的例子中可以看到,我们在操作CRUD的时候,返现有很多重复的代码,比如现在一个UserDao来操作查询操作,写了一段查询代码,然后有一个ProductDao也来操作查询操作,也写了一段查询代码,其实我们会发现这两个查询代码中有很多是重复的,这时候我们就想了,能不能够进行代码的优化,我们想到了模板模式,就是将相同的代码提取出来放到父类中做,不同的代码放到各自的子类中去做,这样重复的代码只会出现一次了,下面来看一下实例,首先我们看一下抽象出来的Dao代码:
- package com.weijia.template;
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- import com.weijia.domain.DaoException;
- import com.weijia.firstdemo.JdbcUtils;
- public abstract class AbstractDao {
- /**
- * 更新
- */
- protected int update(String sql,Object[] args) {
- //这里需要做判断的,可能args为null
- Connection conn = null;
- PreparedStatement st = null;
- try{
- conn = JdbcUtils.getConnection();
- st = conn.prepareStatement(sql);
- for(int i=0;i<args.length;i++){
- st.setObject(i+1, args[i]);
- }
- int count = 0;
- count = st.executeUpdate();
- System.out.println(”更新的记录数:”+count);
- return count;
- }catch(Exception e){
- throw new DaoException(e.getMessage(),e);
- }finally{
- JdbcUtils.free(null, st, conn);
- }
- }
- /**
- * 查询
- * @param sql
- * @param args
- * @return
- */
- protected Object find(String sql,Object[] args){
- Connection conn = null;
- PreparedStatement st = null;
- ResultSet rs = null;
- try{
- conn = JdbcUtils.getConnection();
- st = conn.prepareStatement(sql);
- for(int i=0;i<args.length;i++){
- st.setObject(i+1, args[i]);
- }
- rs = st.executeQuery();
- Object obj = null;
- while(rs.next()){
- //不同的部分放到子类去做
- obj = rowMapper(rs);
- }
- return obj;
- }catch(Exception e){
- throw new DaoException(e.getMessage(),e);
- }finally{
- JdbcUtils.free(null, st, conn);
- }
- }
- //子类需要实现的结果集处理方法
- protected abstract Object rowMapper(ResultSet rs) throws SQLException;
- }
- package com.weijia.template;
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- import com.weijia.domain.DaoException;
- import com.weijia.firstdemo.JdbcUtils;
- public abstract class AbstractDao {
- /**
- * 更新
- */
- protected int update(String sql,Object[] args) {
- //这里需要做判断的,可能args为null
- Connection conn = null;
- PreparedStatement st = null;
- try{
- conn = JdbcUtils.getConnection();
- st = conn.prepareStatement(sql);
- for(int i=0;i<args.length;i++){
- st.setObject(i+1, args[i]);
- }
- int count = 0;
- count = st.executeUpdate();
- System.out.println("更新的记录数:"+count);
- return count;
- }catch(Exception e){
- throw new DaoException(e.getMessage(),e);
- }finally{
- JdbcUtils.free(null, st, conn);
- }
- }
- /**
- * 查询
- * @param sql
- * @param args
- * @return
- */
- protected Object find(String sql,Object[] args){
- Connection conn = null;
- PreparedStatement st = null;
- ResultSet rs = null;
- try{
- conn = JdbcUtils.getConnection();
- st = conn.prepareStatement(sql);
- for(int i=0;i<args.length;i++){
- st.setObject(i+1, args[i]);
- }
- rs = st.executeQuery();
- Object obj = null;
- while(rs.next()){
- //不同的部分放到子类去做
- obj = rowMapper(rs);
- }
- return obj;
- }catch(Exception e){
- throw new DaoException(e.getMessage(),e);
- }finally{
- JdbcUtils.free(null, st, conn);
- }
- }
- //子类需要实现的结果集处理方法
- protected abstract Object rowMapper(ResultSet rs) throws SQLException;
- }
看一下UserDaoImpl类:
- package com.weijia.template;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- import com.weijia.domain.User;
- public class UserDaoImpl extends AbstractDao{
- /**
- * 更新用户信息
- */
- public int update(User user) {
- String sql = ”udpate user set name=?,birthday=?,money=?,where id=?”;
- Object[] args = new Object[]{user.getName(),user.getBirthday(),user.getMoney(),user.getId()};
- return super.update(sql, args);//相同的代码调用父类的方法即可
- }
- /**
- * 删除用户
- * @param user
- */
- public void delete(User user){
- String sql = ”delete from user where id=?”;
- Object[] args = new Object[]{user.getId()};
- super.update(sql, args);
- }
- /**
- * 查找用户
- * @param loginName
- * @param password
- * @return
- */
- public User findUser(String loginName){
- String sql = ”select id,name,money,birthday from user where name=?”;
- Object[] args = new Object[]{loginName};
- return (User)super.find(sql, args);
- }
- @Override
- protected Object rowMapper(ResultSet rs) throws SQLException{
- User user = new User();
- user.setId(rs.getInt(”id”));
- user.setName(rs.getString(”name”));
- user.setMoney(rs.getFloat(”money”));
- user.setBirthday(rs.getDate(”birthday”));
- return user;
- }
- //如果insert的时候不需要获取主键的话,也可以使用super.update方法实现的,这样代码就显得很整洁,相同的代码只需要一份即可(放在父类中)
- //不同的地方放到子类来实现
- //首先要区分哪些是变动的部分,哪些是不变的部分即可
- }
- package com.weijia.template;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- import com.weijia.domain.User;
- public class UserDaoImpl extends AbstractDao{
- /**
- * 更新用户信息
- */
- public int update(User user) {
- String sql = "udpate user set name=?,birthday=?,money=?,where id=?";
- Object[] args = new Object[]{user.getName(),user.getBirthday(),user.getMoney(),user.getId()};
- return super.update(sql, args);//相同的代码调用父类的方法即可
- }
- /**
- * 删除用户
- * @param user
- */
- public void delete(User user){
- String sql = "delete from user where id=?";
- Object[] args = new Object[]{user.getId()};
- super.update(sql, args);
- }
- /**
- * 查找用户
- * @param loginName
- * @param password
- * @return
- */
- public User findUser(String loginName){
- String sql = "select id,name,money,birthday from user where name=?";
- Object[] args = new Object[]{loginName};
- return (User)super.find(sql, args);
- }
- @Override
- protected Object rowMapper(ResultSet rs) throws SQLException{
- User user = new User();
- user.setId(rs.getInt("id"));
- user.setName(rs.getString("name"));
- user.setMoney(rs.getFloat("money"));
- user.setBirthday(rs.getDate("birthday"));
- return user;
- }
- //如果insert的时候不需要获取主键的话,也可以使用super.update方法实现的,这样代码就显得很整洁,相同的代码只需要一份即可(放在父类中)
- //不同的地方放到子类来实现
- //首先要区分哪些是变动的部分,哪些是不变的部分即可
- }
ProductDaoImpl类:
- package com.weijia.template;
- import java.sql.ResultSet;
- public class ProductDaoImpl extends AbstractDao{
- public int update(){
- String sql = ”update product set pname=?,price=? where pid=?”;
- Object[] args = new Object[]{“drug”,11,1};
- return super.update(sql, args);
- }
- @Override
- protected Object rowMapper(ResultSet rs) {
- return null;
- }
- }
- package com.weijia.template;
- import java.sql.ResultSet;
- public class ProductDaoImpl extends AbstractDao{
- public int update(){
- String sql = "update product set pname=?,price=? where pid=?";
- Object[] args = new Object[]{"drug",11,1};
- return super.update(sql, args);
- }
- @Override
- protected Object rowMapper(ResultSet rs) {
- return null;
- }
- }
接着看,现在有一个问题,就是查询,其实update的方式很简单的,完全可以统一化的,因为查询需要处理查询之后的结果集,所以很纠结的,上面的例子中我们看到,我们查询的是一个User对象,假如现在我只是想查询一个用户的name,那么我们只能在写一个findUserName方法了,同时还需要在AbstractDao父类中添加一个抽象方法的行映射器,这种方式就很纠结了,假如我们还有其他的查询需要的话,重复的代码又开始多了,这里我们将采用策略模式进行解决,我们只需要定义行映射器的接口:
- package com.weijia.strategy;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- public interface RowMapper {
- public Object mapRow(ResultSet rs) throws SQLException;
- }
- package com.weijia.strategy;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- public interface RowMapper {
- public Object mapRow(ResultSet rs) throws SQLException;
- }
在父类中只需要修改一下查询的方法:
- /**
- * 查找用户
- * @param sql
- * @param args
- * @param rowMapper
- * @return
- */
- protected Object find(String sql,Object[] args,RowMapper rowMapper){
- Connection conn = null;
- PreparedStatement st = null;
- ResultSet rs = null;
- try{
- conn = JdbcUtils.getConnection();
- st = conn.prepareStatement(sql);
- for(int i=0;i<args.length;i++){
- st.setObject(i+1, args[i]);
- }
- rs = st.executeQuery();
- Object obj = null;
- while(rs.next()){
- obj = rowMapper.mapRow(rs);
- }
- return obj;
- }catch(Exception e){
- throw new DaoException(e.getMessage(),e);
- }finally{
- JdbcUtils.free(null, st, conn);
- }
- }
- /**
- * 查找用户
- * @param sql
- * @param args
- * @param rowMapper
- * @return
- */
- protected Object find(String sql,Object[] args,RowMapper rowMapper){
- Connection conn = null;
- PreparedStatement st = null;
- ResultSet rs = null;
- try{
- conn = JdbcUtils.getConnection();
- st = conn.prepareStatement(sql);
- for(int i=0;i<args.length;i++){
- st.setObject(i+1, args[i]);
- }
- rs = st.executeQuery();
- Object obj = null;
- while(rs.next()){
- obj = rowMapper.mapRow(rs);
- }
- return obj;
- }catch(Exception e){
- throw new DaoException(e.getMessage(),e);
- }finally{
- JdbcUtils.free(null, st, conn);
- }
- }
添加了一个RowMapper接口变量
然后在子类中实现这个接口即可:
- /**
- * 查询名称
- * @param id
- * @return
- */
- public String findUserName(int id){
- String sql = ”select name from user where id=?”;
- Object[] args = new Object[]{id};
- Object user = super.find(sql, args,new RowMapper(){
- public Object mapRow(ResultSet rs) throws SQLException {
- return rs.getObject(“name”);
- }
- });
- return ((User)user).getName();
- }
- /**
- * 采用策略模式:传递不同的行为:C++中可以使用函数指针来实现,Java中可以使用接口的回调来实现
- * @param loginName
- * @param password
- * @return
- */
- public User findUser(String loginName){
- String sql = ”select id,name,money,birthday from user where name=?”;
- Object[] args = new Object[]{loginName};
- return (User)super.find(sql, args,new RowMapper(){
- public Object mapRow(ResultSet rs) throws SQLException {
- User user = new User();
- user.setId(rs.getInt(”id”));
- user.setName(rs.getString(”name”));
- user.setMoney(rs.getFloat(”money”));
- user.setBirthday(rs.getDate(”birthday”));
- return user;
- }
- });
- }
- /**
- * 查询名称
- * @param id
- * @return
- */
- public String findUserName(int id){
- String sql = "select name from user where id=?";
- Object[] args = new Object[]{id};
- Object user = super.find(sql, args,new RowMapper(){
- public Object mapRow(ResultSet rs) throws SQLException {
- return rs.getObject("name");
- }
- });
- return ((User)user).getName();
- }
- /**
- * 采用策略模式:传递不同的行为:C++中可以使用函数指针来实现,Java中可以使用接口的回调来实现
- * @param loginName
- * @param password
- * @return
- */
- public User findUser(String loginName){
- String sql = "select id,name,money,birthday from user where name=?";
- Object[] args = new Object[]{loginName};
- return (User)super.find(sql, args,new RowMapper(){
- public Object mapRow(ResultSet rs) throws SQLException {
- User user = new User();
- user.setId(rs.getInt("id"));
- user.setName(rs.getString("name"));
- user.setMoney(rs.getFloat("money"));
- user.setBirthday(rs.getDate("birthday"));
- return user;
- }
- });
- }
通过上面的CRUD优化之后,我们在进行操作的时候,代码编写是很方便和简洁的
Spring框架中的JdbcTemplate
说完了上面的我们自定义的CRUD模板,下面我来看一下spring框架给我们提供的CRUD模板(JdbcTemplate),其实他的实现原理和我们上面是一样的,只是他的功能会更强。
下面来看一下实例代码:
- package com.weijia.springtemplate;
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- import java.sql.Statement;
- import java.util.List;
- import java.util.Map;
- import org.springframework.dao.DataAccessException;
- import org.springframework.jdbc.core.BeanPropertyRowMapper;
- import org.springframework.jdbc.core.ConnectionCallback;
- import org.springframework.jdbc.core.JdbcTemplate;
- import org.springframework.jdbc.core.RowMapper;
- import com.weijia.domain.User;
- import com.weijia.firstdemo.JdbcUtils;
- public class JdbcTemplateTest {
- public static void main(String[] args){
- User user = new User();
- user.setMoney(20);
- user.setId(1);
- update(user);
- }
- /**
- * 更新操作
- * @param user
- */
- static void update(User user){
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- String sql = ”update user set money=? where id=?”;
- Object[] args = new Object[]{user.getMoney(),user.getId()};
- jdbc.update(sql, args);
- }
- /**
- * 通过用户名查询用户
- * @param name
- * @return
- */
- static User findUser(String name){
- //需要传递一个数据源
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- String sql = ”select id,name,money,birthday from user where name=?”;
- Object[] args = new Object[]{name};
- //queryForObject方法和我们之前采用策略模式设置的模板很类似呀,这个方法只会返回一个记录,如果有多个记录返回或者没有记录返回的话,这个方法就会报告异常的
- Object user = jdbc.queryForObject(sql,args,new RowMapper(){
- public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
- User user = new User();
- user.setId(rs.getInt(”id”));
- user.setName(rs.getString(”name”));
- user.setMoney(rs.getFloat(”money”));
- user.setBirthday(rs.getDate(”birthday”));
- return user;
- }});
- return (User)user;
- }
- /**
- * 通过用户名查询实体类
- * @param name
- * @return
- */
- static User findUsers(String name){
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- String sql = ”select id,name,money,birthday from user where name=?”;
- Object[] args = new Object[]{name};
- //如果没有记录或者返回多个记录的话,这个方法是会报异常的
- //使用这个方法直接将返回的结果集映射到实体类,这里返回的结果集中的字段和实体类中的属性名必须相等
- //如果不相等的话,就是用默认值对其属性进行赋值
- Object user = jdbc.queryForObject(sql,args,new BeanPropertyRowMapper(User.class){});
- return (User)user;
- }
- /**
- * 查询多个用户
- * @param id
- * @return
- */
- static List findUser1(int id){
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- String sql = ”select id,name,money,birthday from user where id<?”;
- Object[] args = new Object[]{id};
- List users = jdbc.query(sql,args,new BeanPropertyRowMapper(User.class){});
- return users;
- }
- //求最大值,记录总数等情况,查询结果只有一个值
- //返回8种基本类型
- static int getUserCount(){
- String sql = ”select count(*) from user”;
- //JdbcTemplate是线程安全的
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- return jdbc.queryForInt(sql);
- }
- //返回String
- static String getUserName(int id){
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- String sql = ”select name from user where id=”+id;
- Object name = jdbc.queryForObject(sql, String.class);
- return (String)name;
- }
- //返回map
- static Map getUser(int id){
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- String sql = ”select id,name,birthday from user where id=?”;
- return jdbc.queryForMap(sql,new Object[]{id});
- }
- //添加完用户之后返回主键
- static User addUser(final User user){
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- //这个和RowMapper接口差不多,RowMapper是传回来一个ResultSet
- //而这个接口返回的是一个Connection,给我们更多的权限了
- jdbc.execute(new ConnectionCallback(){
- public Object doInConnection(Connection conn) throws SQLException,DataAccessException {
- String sql = ”insert into user(name,birtdhday,birthday) values(‘jiangwei’,’1987-01-01’,400)”;
- PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
- ps.executeUpdate();
- //可能是组合主键,可能会返回一个ResultSet
- ResultSet rs = ps.getGeneratedKeys();
- if(rs.next()){
- user.setId(rs.getInt(1));
- }
- return null;
- }});
- return user;
- }
- }
- package com.weijia.springtemplate;
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- import java.sql.Statement;
- import java.util.List;
- import java.util.Map;
- import org.springframework.dao.DataAccessException;
- import org.springframework.jdbc.core.BeanPropertyRowMapper;
- import org.springframework.jdbc.core.ConnectionCallback;
- import org.springframework.jdbc.core.JdbcTemplate;
- import org.springframework.jdbc.core.RowMapper;
- import com.weijia.domain.User;
- import com.weijia.firstdemo.JdbcUtils;
- public class JdbcTemplateTest {
- public static void main(String[] args){
- User user = new User();
- user.setMoney(20);
- user.setId(1);
- update(user);
- }
- /**
- * 更新操作
- * @param user
- */
- static void update(User user){
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- String sql = "update user set money=? where id=?";
- Object[] args = new Object[]{user.getMoney(),user.getId()};
- jdbc.update(sql, args);
- }
- /**
- * 通过用户名查询用户
- * @param name
- * @return
- */
- static User findUser(String name){
- //需要传递一个数据源
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- String sql = "select id,name,money,birthday from user where name=?";
- Object[] args = new Object[]{name};
- //queryForObject方法和我们之前采用策略模式设置的模板很类似呀,这个方法只会返回一个记录,如果有多个记录返回或者没有记录返回的话,这个方法就会报告异常的
- Object user = jdbc.queryForObject(sql,args,new RowMapper(){
- public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
- User user = new User();
- user.setId(rs.getInt("id"));
- user.setName(rs.getString("name"));
- user.setMoney(rs.getFloat("money"));
- user.setBirthday(rs.getDate("birthday"));
- return user;
- }});
- return (User)user;
- }
- /**
- * 通过用户名查询实体类
- * @param name
- * @return
- */
- static User findUsers(String name){
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- String sql = "select id,name,money,birthday from user where name=?";
- Object[] args = new Object[]{name};
- //如果没有记录或者返回多个记录的话,这个方法是会报异常的
- //使用这个方法直接将返回的结果集映射到实体类,这里返回的结果集中的字段和实体类中的属性名必须相等
- //如果不相等的话,就是用默认值对其属性进行赋值
- Object user = jdbc.queryForObject(sql,args,new BeanPropertyRowMapper(User.class){});
- return (User)user;
- }
- /**
- * 查询多个用户
- * @param id
- * @return
- */
- static List findUser1(int id){
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- String sql = "select id,name,money,birthday from user where id<?";
- Object[] args = new Object[]{id};
- List users = jdbc.query(sql,args,new BeanPropertyRowMapper(User.class){});
- return users;
- }
- //求最大值,记录总数等情况,查询结果只有一个值
- //返回8种基本类型
- static int getUserCount(){
- String sql = "select count(*) from user";
- //JdbcTemplate是线程安全的
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- return jdbc.queryForInt(sql);
- }
- //返回String
- static String getUserName(int id){
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- String sql = "select name from user where id="+id;
- Object name = jdbc.queryForObject(sql, String.class);
- return (String)name;
- }
- //返回map
- static Map getUser(int id){
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- String sql = "select id,name,birthday from user where id=?";
- return jdbc.queryForMap(sql,new Object[]{id});
- }
- //添加完用户之后返回主键
- static User addUser(final User user){
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- //这个和RowMapper接口差不多,RowMapper是传回来一个ResultSet
- //而这个接口返回的是一个Connection,给我们更多的权限了
- jdbc.execute(new ConnectionCallback(){
- public Object doInConnection(Connection conn) throws SQLException,DataAccessException {
- String sql = "insert into user(name,birtdhday,birthday) values('jiangwei','1987-01-01',400)";
- PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
- ps.executeUpdate();
- //可能是组合主键,可能会返回一个ResultSet
- ResultSet rs = ps.getGeneratedKeys();
- if(rs.next()){
- user.setId(rs.getInt(1));
- }
- return null;
- }});
- return user;
- }
- }
下面来看一下,JdbcTemplate的相关使用方法:
首先看一下,我们开始使用一个数据源来初始化一个JdbcTemplate模板
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
update(String sql,Object[] args):第一个参数是sql语句,第二参数是需要填充的更新参数
queryForObject(String sql,Object[] args, RowMapper rowMapper):第一参数是sql语句,第二参数是需要填充的查询参数,第三个参数是行映射器(和前面我们设计的一样),这个方法只适用查询结果是一个的情况,如果查询结果是多个的话,这个方法会报异常的,同时这个方法第三个参数我们也可以传递一个:new BeanPropertyRowMapper(User.class){}对象,这个就可以将查询结果填充到User实体类中了,当然这里有一个限制就是要求查询出来的结果集中的字段名和实体类中的属性名一样,其实这内部使用的是反射技术来实现的,我们之前写过这样的方法的。query(String sql,Object[]args,RowMapper rowMapper):这个方法和上面的那个方法不同的就是返回的结果,这个方法返回的是一个List,针对于查询结果是多个的情况
queryForInt(String sql,Object[] args):这个方法是针对查询结果是一个整型的,比如我们需要查询出用户的总数
queryForLong(String sql,Object[] args):这个方法是查询出long类型的
queryForObject(String sql, Class requiredType):这个方法是对于那些没有特定查询类型的方法同一使用这个方法,比如现在想查询一个用户的名称是String类型的,或者想查询用户的money,是float类型的,这里我们只需要在第二个参数中指定类型即可
queryForMap(String sql,Object[] args):查询返回的是一个Map集合
queryForList(String sql,Object[] args):查询返回的是一个List集合
上面的方法我们就足够了
下面再来看一个需求,如果我们想得到插入一条记录之后的主键的操作,这里改如何操作呢?
在之前我们操作的是需要将PreparedStatement中设置一个参数,不然会报错的,我们通过上面的方法可以知道其内部都是使用PreparedStatement实现的,因为有占位符,需要设置查询参数的。但是他并没有提供一个方法能够设置这个PreparedStatement的一些参数,但是如果我们想获取到主键值的话,必须要设置PreparedStatement的第二参数为:Statement.RETURN_GENERATED_KEYS,那么这时候,JdbcTemplate还给我们提供了一个方法:
- jdbc.execute(new ConnectionCallback(){
- public Object doInConnection(Connection con) throws SQLException,DataAccessException {
- //do something
- return null;
- }
- });
- jdbc.execute(new ConnectionCallback(){
- public Object doInConnection(Connection con) throws SQLException,DataAccessException {
- //do something
- return null;
- }
- });
这样一来,上面提到的JdbcTemplate中的方法就可以满足我们的日常需求了
加强版的JdbcTemplate
1.NamedParameterJdbcTemplate
这个模板其实我们见名知意,他的最大的作用应该是参数名称的功能,我们在上面的模板中可以看到,我们每次传递的参数数组中的参数顺序必须和查询语句中的占位符的顺序要相同,但是这个模板给我们提供了一个便捷的好处就是,不需要关心参数的顺序:
- static User findUsers(User user){
- String sql = ”select id,name,money,birthday from user where name=:n and money>:m and id<:id”;
- //参数的顺序必须一致,不然报错
- //Object[] args = new Object[]{user.getName(),user.getMoney(),user.getId()};
- //如果没有记录或者返回多个记录的话,这个方法是会报异常的
- //使用Map代替数据进行传递参数,这样就不需要在乎传递的顺序了(其实内部源代码是先解析这个map,将从新组装sql然后交给JdbcTemplate来处理)
- Map<String,Object> params = new HashMap<String,Object>();
- params.put(”n”, user.getName());
- params.put(”m”, user.getMoney());
- params.put(”id”, user.getId());
- Object users = named.queryForObject(sql, params, new BeanPropertyRowMapper(User.class));
- return (User)users;
- }
- static User findUsers(User user){
- String sql = "select id,name,money,birthday from user where name=:n and money>:m and id<:id";
- //参数的顺序必须一致,不然报错
- //Object[] args = new Object[]{user.getName(),user.getMoney(),user.getId()};
- //如果没有记录或者返回多个记录的话,这个方法是会报异常的
- //使用Map代替数据进行传递参数,这样就不需要在乎传递的顺序了(其实内部源代码是先解析这个map,将从新组装sql然后交给JdbcTemplate来处理)
- Map<String,Object> params = new HashMap<String,Object>();
- params.put("n", user.getName());
- params.put("m", user.getMoney());
- params.put("id", user.getId());
- Object users = named.queryForObject(sql, params, new BeanPropertyRowMapper(User.class));
- return (User)users;
- }
:参数名
这个参数名,我们会在下面的HashMap中当做key来进行参数的填充,然后将这个HashMap变量传递到queryForObject方法中,而不是在使用数组的方式了,这种方式其实就是将数组类型的参数传递变成了Map类型的参数传递,其实方法的功能都是没有改变的,只是相对应的方法中的参数不再是Object[]数组了,而是Map集合类型的
同样的假如是JavaBean类型的怎么填充参数呢?这里也提供了一个方法:
- SqlParameterSource ps = new BeanPropertySqlParameterSource(user);
- SqlParameterSource ps = new BeanPropertySqlParameterSource(user);
这样直接进行参数的填充,但是这里有一个限制就是sql中的参数名称必须要和JavaBean中的属性名称一样(内部使用反射技术实现的)
最后我们获取主键的值的方式也变化了:
- SqlParameterSource ps = new BeanPropertySqlParameterSource(user);
- //将插入之后的记录的id放到keyHolder中
- KeyHolder keyHolder = new GeneratedKeyHolder();
- named.update(sql, ps, keyHolder);
- //也有可能是组合主键
- Map map = keyHolder.getKeys();
- int id = keyHolder.getKey().intValue();
- SqlParameterSource ps = new BeanPropertySqlParameterSource(user);
- //将插入之后的记录的id放到keyHolder中
- KeyHolder keyHolder = new GeneratedKeyHolder();
- named.update(sql, ps, keyHolder);
- //也有可能是组合主键
- Map map = keyHolder.getKeys();
- int id = keyHolder.getKey().intValue();
这样比JdbcTemplate中的方法更简单了。
下面看一下完整的实例代码:
- package com.weijia.springtemplate;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.Map;
- import org.springframework.jdbc.core.BeanPropertyRowMapper;
- import org.springframework.jdbc.core.JdbcTemplate;
- import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
- import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
- import org.springframework.jdbc.core.namedparam.SqlParameterSource;
- import org.springframework.jdbc.support.GeneratedKeyHolder;
- import org.springframework.jdbc.support.KeyHolder;
- import com.weijia.domain.User;
- import com.weijia.firstdemo.JdbcUtils;
- /**
- * 可以实现命名参数
- * 这样就不会担心占位符?和传递参数的位置了错乱的问题了
- * 将数组参数变成hashmap类型的参数
- * @author weijiang204321
- *
- */
- public class NamedJdbcTemplate {
- public static void main(String[] args) throws Exception{
- System.out.println(”add id:”+addUser(new User(1,“jiangwei”,new Date(System.currentTimeMillis()),100)));
- }
- //其内部有一个JdbcTemplate对象,有些事情还是交给JdbcTemplate来处理的(静态代理)
- public static NamedParameterJdbcTemplate named = new NamedParameterJdbcTemplate(JdbcUtils.getDataSource());
- static User findUser(User user){
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- String sql = ”select id,name,money,birthday from user where name=? and money>? and id<?”;
- //参数的顺序必须一致,不然报错
- Object[] args = new Object[]{user.getName(),user.getMoney(),user.getId()};
- //如果没有记录或者返回多个记录的话,这个方法是会报异常的
- Object users = jdbc.queryForObject(sql,args,new BeanPropertyRowMapper(User.class){});
- return (User)users;
- }
- static User findUsers(User user){
- String sql = ”select id,name,money,birthday from user where name=:n and money>:m and id<:id”;
- //参数的顺序必须一致,不然报错
- //Object[] args = new Object[]{user.getName(),user.getMoney(),user.getId()};
- //如果没有记录或者返回多个记录的话,这个方法是会报异常的
- //使用Map代替数据进行传递参数,这样就不需要在乎传递的顺序了(其实内部源代码是先解析这个map,将从新组装sql然后交给JdbcTemplate来处理)
- Map<String,Object> params = new HashMap<String,Object>();
- params.put(”n”, user.getName());
- params.put(”m”, user.getMoney());
- params.put(”id”, user.getId());
- Object users = named.queryForObject(sql, params, new BeanPropertyRowMapper(User.class));
- return (User)users;
- }
- static User findUser1(User user){
- String sql = ”select id,name,money,birthday from user where name=:name and money>:money and id<:id”;
- //参数名必须和User中的属性名相同的,内部还是通过反射技术实现的
- SqlParameterSource ps = new BeanPropertySqlParameterSource(user);
- Object users = named.queryForObject(sql, ps, new BeanPropertyRowMapper(User.class));
- return (User)users;
- }
- static int addUser(User user){
- String sql = ”insert into user(name,birthday,money) values(:name,:birthday,:money)”;
- //参数名必须和User中的属性名相同的,内部还是通过反射技术实现的
- SqlParameterSource ps = new BeanPropertySqlParameterSource(user);
- //将插入之后的记录的id放到keyHolder中
- KeyHolder keyHolder = new GeneratedKeyHolder();
- named.update(sql, ps, keyHolder);
- //也有可能是组合主键
- Map map = keyHolder.getKeys();
- int id = keyHolder.getKey().intValue();
- return keyHolder.getKey().intValue();
- }
- }
- package com.weijia.springtemplate;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.Map;
- import org.springframework.jdbc.core.BeanPropertyRowMapper;
- import org.springframework.jdbc.core.JdbcTemplate;
- import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
- import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
- import org.springframework.jdbc.core.namedparam.SqlParameterSource;
- import org.springframework.jdbc.support.GeneratedKeyHolder;
- import org.springframework.jdbc.support.KeyHolder;
- import com.weijia.domain.User;
- import com.weijia.firstdemo.JdbcUtils;
- /**
- * 可以实现命名参数
- * 这样就不会担心占位符?和传递参数的位置了错乱的问题了
- * 将数组参数变成hashmap类型的参数
- * @author weijiang204321
- *
- */
- public class NamedJdbcTemplate {
- public static void main(String[] args) throws Exception{
- System.out.println("add id:"+addUser(new User(1,"jiangwei",new Date(System.currentTimeMillis()),100)));
- }
- //其内部有一个JdbcTemplate对象,有些事情还是交给JdbcTemplate来处理的(静态代理)
- public static NamedParameterJdbcTemplate named = new NamedParameterJdbcTemplate(JdbcUtils.getDataSource());
- static User findUser(User user){
- JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
- String sql = "select id,name,money,birthday from user where name=? and money>? and id<?";
- //参数的顺序必须一致,不然报错
- Object[] args = new Object[]{user.getName(),user.getMoney(),user.getId()};
- //如果没有记录或者返回多个记录的话,这个方法是会报异常的
- Object users = jdbc.queryForObject(sql,args,new BeanPropertyRowMapper(User.class){});
- return (User)users;
- }
- static User findUsers(User user){
- String sql = "select id,name,money,birthday from user where name=:n and money>:m and id<:id";
- //参数的顺序必须一致,不然报错
- //Object[] args = new Object[]{user.getName(),user.getMoney(),user.getId()};
- //如果没有记录或者返回多个记录的话,这个方法是会报异常的
- //使用Map代替数据进行传递参数,这样就不需要在乎传递的顺序了(其实内部源代码是先解析这个map,将从新组装sql然后交给JdbcTemplate来处理)
- Map<String,Object> params = new HashMap<String,Object>();
- params.put("n", user.getName());
- params.put("m", user.getMoney());
- params.put("id", user.getId());
- Object users = named.queryForObject(sql, params, new BeanPropertyRowMapper(User.class));
- return (User)users;
- }
- static User findUser1(User user){
- String sql = "select id,name,money,birthday from user where name=:name and money>:money and id<:id";
- //参数名必须和User中的属性名相同的,内部还是通过反射技术实现的
- SqlParameterSource ps = new BeanPropertySqlParameterSource(user);
- Object users = named.queryForObject(sql, ps, new BeanPropertyRowMapper(User.class));
- return (User)users;
- }
- static int addUser(User user){
- String sql = "insert into user(name,birthday,money) values(:name,:birthday,:money)";
- //参数名必须和User中的属性名相同的,内部还是通过反射技术实现的
- SqlParameterSource ps = new BeanPropertySqlParameterSource(user);
- //将插入之后的记录的id放到keyHolder中
- KeyHolder keyHolder = new GeneratedKeyHolder();
- named.update(sql, ps, keyHolder);
- //也有可能是组合主键
- Map map = keyHolder.getKeys();
- int id = keyHolder.getKey().intValue();
- return keyHolder.getKey().intValue();
- }
- }
2.SimpleJdbcTemplate
见名知意,这个模板会操作变得更简单的,他的主要变化就是两点:
第一、实现可变参数了
第二、查询出来的对象无须进行类型转换了- //将类型也作为参数传入的,无须进行类型转换操作了
- static User findUser(int id,String name,Class<User> clazz){
- String sql = ”select id,name,money,birthday from user where id=? and name=?”;
- //参数名必须和User中的属性名相同的,内部还是通过反射技术实现的
- //simple.getNamedParameterJdbcOperations();//得到一个NamedParameterJdbcOperateions对象
- //simple.getJdbcOperations();//得到Jdbc对象
- //这个方法在传递参数的时候使用的是可变参数,参数的顺序和占位符?要必须一致
- return simple.queryForObject(sql, ParameterizedBeanPropertyRowMapper.newInstance(clazz),id,name);
- }
- //将类型也作为参数传入的,无须进行类型转换操作了
- static User findUser(int id,String name,Class<User> clazz){
- String sql = "select id,name,money,birthday from user where id=? and name=?";
- //参数名必须和User中的属性名相同的,内部还是通过反射技术实现的
- //simple.getNamedParameterJdbcOperations();//得到一个NamedParameterJdbcOperateions对象
- //simple.getJdbcOperations();//得到Jdbc对象
- //这个方法在传递参数的时候使用的是可变参数,参数的顺序和占位符?要必须一致
- return simple.queryForObject(sql, ParameterizedBeanPropertyRowMapper.newInstance(clazz),id,name);
- }
最后一个参数其实是可变参数,可以传递多个值的,但是这个传递的值的顺序必须要和上面sql中占位符的顺序一致,下面是测试代码:
- System.out.println(findUser(1505,“jiangwei”,User.class));
- System.out.println(findUser(1505,"jiangwei",User.class));
总结:
至此我们就将JDBC的相关知识都介绍完毕了,因为JDBC本身的内容是很多的,我们也只有遇到问题的时候采取解决,上面的只是对于开发来说应该没有多大的问题了,可能会有一些细节上的问题,这个只能在后续进行完善了。上面说到的内容,可能有些不全,我将整个讲解的项目功能放到了网上,下载地址:
http://download.csdn.net/detail/jiangwei0910410003/7373133
这个需要注意的是一定记得连接上正确的数据库,这个可以查看连接数据库的那段代码。。
如果发现有问题,请及时提醒,我做修改,希望能够和大家一起做到百分百的成功。。
转自:http://blog.csdn.net/jiangsanfeng1111/article/details/52607209# -
JDBC 详解 (二)
- 我们看到,在我们自定义的数据源中,主要有这么几个变量:
初始化连接数,最大连接数,当前的连接数,连接池(因为我们可能需要频繁的添加连接和删除连接所以使用LinkedList,因为这个list是链表结构的,增加和删除效率高)
主要流程是:初始化数据源的时候,初始化一定量的连接放到池子中,当用户使用getConnection()方法取出连接的时候,我们会判断这个连接池中还有没有连接了,有就直接取出第一个连接返回,没有的话,我们在判断当前的连接数有没有超过最大连接数,超过的话,就抛出一个异常(其实这里还可以选择等待其他连接的释放,这个具体实现是很麻烦的),没有超过的话,就创建连接,并且将其放入池子中。
我们自定义的数据源是实现了JDBC中的DataSource接口的,这个接口很重要的,后面我们会说到apache的数据源都是要实现这个接口的,这个接口统一了数据源的标准,这个接口中有很多实现的,所以看到我们的数据源类中有很多没必要的方法,但是那个方法都是要实现的,最重要的就是要实现getConnection方法,其他的实现都只需要调用super.XXX就可以了。
在JdbcUtils类中我们也是需要修改的,首先我们要在静态代码块中初始化我们的数据源,在getConnection方法中调用数据源的getConnection方法,在free方法中调用数据源的free方法即可。
看一下测试类:
运行结果:
我们可以看到,我们在测试代码中申请了10个连接,从结果上可以看出前五个是不同的连接,后五个连接和前五个是一样的,这是因为我们在释放连接的时候就是free方法中,是将连接重新放到池子中的,上面显示的是五个,是因为我们初始化的连接数是5个,当第一个连接释放的时候这个连接其实已经放到了池子的第六个位置,以此类推。
下面我们继续来看下个问题,我们在上面的数据源中可以看到,我们定义了一个free方法来释放连接的,然后在JdbcUtils中调用这个方法即可,但是这个貌似不太符合我们的使用习惯,因为之前我们看到我们释放连接的时候都是使用close方法的,所以这里面我们在修改一下,至于怎么修改呢?
首先我们知道那个close方法是JDBC中的Connection接口中的,所有自定义的连接都是需要实现这个接口的,那么我们如果我们想让我们free中的逻辑放到close中的话,就需要实现这个接口了,我们可以看到
-
JDBC中CRUD的模板模式
我们从前面的例子中可以看到,我们在操作CRUD的时候,返现有很多重复的代码,比如现在一个UserDao来操作查询操作,写了一段查询代码,然后有一个ProductDao也来操作查询操作,也写了一段查询代码,其实我们会发现这两个查询代码中有很多是重复的,这时候我们就想了,能不能够进行代码的优化,我们想到了模板模式,就是将相同的代码提取出来放到父类中做,不同的代码放到各自的子类中去做,这样重复的代码只会出现一次了,下面来看一下实例,首先我们看一下抽象出来的Dao代码:
看一下UserDaoImpl类:
ProductDaoImpl类:
接着看,现在有一个问题,就是查询,其实update的方式很简单的,完全可以统一化的,因为查询需要处理查询之后的结果集,所以很纠结的,上面的例子中我们看到,我们查询的是一个User对象,假如现在我只是想查询一个用户的name,那么我们只能在写一个findUserName方法了,同时还需要在AbstractDao父类中添加一个抽象方法的行映射器,这种方式就很纠结了,假如我们还有其他的查询需要的话,重复的代码又开始多了,这里我们将采用策略模式进行解决,我们只需要定义行映射器的接口:
在父类中只需要修改一下查询的方法:
添加了一个RowMapper接口变量
然后在子类中实现这个接口即可:
通过上面的CRUD优化之后,我们在进行操作的时候,代码编写是很方便和简洁的
Spring框架中的JdbcTemplate
说完了上面的我们自定义的CRUD模板,下面我来看一下spring框架给我们提供的CRUD模板(JdbcTemplate),其实他的实现原理和我们上面是一样的,只是他的功能会更强。
下面来看一下实例代码:
下面来看一下,JdbcTemplate的相关使用方法:
首先看一下,我们开始使用一个数据源来初始化一个JdbcTemplate模板
update(String sql,Object[] args):第一个参数是sql语句,第二参数是需要填充的更新参数
queryForObject(String sql,Object[] args, RowMapper rowMapper):第一参数是sql语句,第二参数是需要填充的查询参数,第三个参数是行映射器(和前面我们设计的一样),这个方法只适用查询结果是一个的情况,如果查询结果是多个的话,这个方法会报异常的,同时这个方法第三个参数我们也可以传递一个:new BeanPropertyRowMapper(User.class){}对象,这个就可以将查询结果填充到User实体类中了,当然这里有一个限制就是要求查询出来的结果集中的字段名和实体类中的属性名一样,其实这内部使用的是反射技术来实现的,我们之前写过这样的方法的。query(String sql,Object[]args,RowMapper rowMapper):这个方法和上面的那个方法不同的就是返回的结果,这个方法返回的是一个List,针对于查询结果是多个的情况
queryForInt(String sql,Object[] args):这个方法是针对查询结果是一个整型的,比如我们需要查询出用户的总数
queryForLong(String sql,Object[] args):这个方法是查询出long类型的
queryForObject(String sql, Class requiredType):这个方法是对于那些没有特定查询类型的方法同一使用这个方法,比如现在想查询一个用户的名称是String类型的,或者想查询用户的money,是float类型的,这里我们只需要在第二个参数中指定类型即可
queryForMap(String sql,Object[] args):查询返回的是一个Map集合
queryForList(String sql,Object[] args):查询返回的是一个List集合
上面的方法我们就足够了
下面再来看一个需求,如果我们想得到插入一条记录之后的主键的操作,这里改如何操作呢?
在之前我们操作的是需要将PreparedStatement中设置一个参数,不然会报错的,我们通过上面的方法可以知道其内部都是使用PreparedStatement实现的,因为有占位符,需要设置查询参数的。但是他并没有提供一个方法能够设置这个PreparedStatement的一些参数,但是如果我们想获取到主键值的话,必须要设置PreparedStatement的第二参数为:Statement.RETURN_GENERATED_KEYS,那么这时候,JdbcTemplate还给我们提供了一个方法:
这样一来,上面提到的JdbcTemplate中的方法就可以满足我们的日常需求了
加强版的JdbcTemplate
1.NamedParameterJdbcTemplate
这个模板其实我们见名知意,他的最大的作用应该是参数名称的功能,我们在上面的模板中可以看到,我们每次传递的参数数组中的参数顺序必须和查询语句中的占位符的顺序要相同,但是这个模板给我们提供了一个便捷的好处就是,不需要关心参数的顺序:
:参数名
这个参数名,我们会在下面的HashMap中当做key来进行参数的填充,然后将这个HashMap变量传递到queryForObject方法中,而不是在使用数组的方式了,这种方式其实就是将数组类型的参数传递变成了Map类型的参数传递,其实方法的功能都是没有改变的,只是相对应的方法中的参数不再是Object[]数组了,而是Map集合类型的
同样的假如是JavaBean类型的怎么填充参数呢?这里也提供了一个方法:
这样直接进行参数的填充,但是这里有一个限制就是sql中的参数名称必须要和JavaBean中的属性名称一样(内部使用反射技术实现的)
最后我们获取主键的值的方式也变化了:
这样比JdbcTemplate中的方法更简单了。
下面看一下完整的实例代码:
2.SimpleJdbcTemplate
见名知意,这个模板会操作变得更简单的,他的主要变化就是两点:
第一、实现可变参数了
第二、查询出来的对象无须进行类型转换了
最后一个参数其实是可变参数,可以传递多个值的,但是这个传递的值的顺序必须要和上面sql中占位符的顺序一致,下面是测试代码:
总结:
至此我们就将JDBC的相关知识都介绍完毕了,因为JDBC本身的内容是很多的,我们也只有遇到问题的时候采取解决,上面的只是对于开发来说应该没有多大的问题了,可能会有一些细节上的问题,这个只能在后续进行完善了。上面说到的内容,可能有些不全,我将整个讲解的项目功能放到了网上,下载地址:
http://download.csdn.net/detail/jiangwei0910410003/7373133
这个需要注意的是一定记得连接上正确的数据库,这个可以查看连接数据库的那段代码。。
如果发现有问题,请及时提醒,我做修改,希望能够和大家一起做到百分百的成功。。
转自:http://blog.csdn.net/jiangsanfeng1111/article/details/52607209#