• Java的JDBC原生态学习以及连接池的用法


    JDBC是什么

    JDBC(Java Data Base Connectivity)是Java访问数据库的桥梁,但它只是接口规范,具体实现是各数据库厂商提供的驱动程序(Driver)。

    应用程序、JDBC、JDBC驱动、数据库之间的关系如上图:应用程序通过JDBC访问数据库,而JDBC驱动来具体实现JDBC访问数据库的动作。

    JDBC版本

    JDBC的版本和JDK版本是独立的,他们的对应关系:

    JDK版本DB版本
    JDK 1.1 JDBC1
    JDK 1.2, 1.3 JDBC2
    JDK 1.4, 1.5 JDBC3
    JDK 1.6 JDBC4
    JDK 1.7 JDBC4.1

    JDBC4.0新特性

    JDBC 4.0中增加的主要特性包括:

    • JDBC驱动类的自动加载;
      不必再使用Class.forName()方法明确加载JDBC驱动。当调用getConnection方法 时,DriverManager会尝试从初始化时已经加载的JDBC驱动程序库中选择合适的驱动,以及他在当前应用的同一个类加载器中明确加载使用过的驱动。
    • 连接管理的增强
    • 在JDBC 4.0之前,我们依靠JDBC URL来定义一个数据源连接。现在我们只需为标准连接工厂机制提供一组参数,就能获取与任何数据源的连接。
    • 对RowId SQL类型的支持
      增加了RowID接口以支持ROWID数据类型。
    • SQL的DataSet实现使用了Annotations;
      JDBC 4.0对标注(Java SE 5新增)作了进一步规范和补充,他允许开发人员不必再写大量代码就能达到联系SQL查询和Java类的目的。
    • SQL异常处理的增强;
      JDBC 4.0在SQLException处理中有不少增强,在处理SQLException时提供了更好的开发体验,如新的SQLException子类、对因果关系的支持、对改进的for-each循环的支持。
    • 对SQL XML的支持;

    JDBC链接步骤

    1. 链接数据库;
    2. 创建执行声明;
    3. 执行Sql语句;
    4. 处理结果;
    5. 释放资源;

    pom.xml添加MySql驱动依赖:

    <dependency>
    	<groupId>mysql</groupId>
    	<artifactId>mysql-connector-java</artifactId>
    	<version>5.1.30</version>
    </dependency>

    完整的App.java代码

    package net.oseye.DbDemo;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.ResultSet;
    import java.sql.Statement;
    
    public class App 
    {
        public static void main( String[] args ) throws Exception
        {
        	//链接数据库
            Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root","root");
            //创建执行声明
            Statement stmt=conn.createStatement();
            //执行Sql语句
            ResultSet rs=stmt.executeQuery("select user_id,user_name from user");
            //处理结果集
            while(rs.next()){
            	System.out.println("用户ID"+rs.getInt(1)+"	用户名:"+rs.getString(2));
            }
            //释放资源
            rs.close();
            stmt.close();
            conn.close();
        }
    }

    相关API在此不做详细介绍,请查看JDK API文档。

    JDBC预处理PreparedStatement

    上面的示例有两个缺点:

    1. 使用DriverManager获得数据库连接。这种方式效率低,并且其性能、可靠性和稳定性随着用户访问量得增加逐渐下降,应该使用连接池取而代之(后文再讲);
    2. 应该使用PreparedStatement取代Statement,因为Statement的缺点也非常明显:
      • 执行时发送sql,影响效率.
      • 同样的sql,每次都要发送,不能进行有效的缓存,是一种资源的浪费.
      • 示例代码中演示可以看出,为了防止恶意数据我们还需要编写附加的程序(过滤器)带来不必要的开支.
      • 拼接sql字符串很容易出现错误.

    PreparedStatement是Statement的子接口,它在执行sql之前首先准备好sql语句,将其中的条件通过?进行占位,还有一个好处就是,同样的sql会被PreparedStatement有效的缓存,也就是说,数据库会减少校验的过程,减少消耗,这就是我们常说的预处理命令方式。

    package net.oseye.DbDemo;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    
    public class App 
    {
        public static void main( String[] args ) throws Exception
        {
        	//链接数据库
            Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root","root");
            //创建执行声明
            PreparedStatement pstmt=conn.prepareStatement("select user_id,user_name from user where user_id < ?");
            //填充占位符?
            pstmt.setInt(1, 3);
            //执行Sql语句
            ResultSet rs=pstmt.executeQuery();
            //处理结果集
            while(rs.next()){
            	System.out.println("用户ID"+rs.getInt(1)+"	用户名:"+rs.getString(2));
            }
            //释放资源
            rs.close();
            pstmt.close();
            conn.close();
        }
    }

    JDBC调用存储过程请使用“CallableStatement”接口。

    JDBC事务

    数据库的事务是保证数据完整性的一种机制,简而言之,就是怎样确保数据的执行过程要么都成功,要么都失败

    • 原子性(atomicity):组成事务处理的语句形成了一个逻辑单元,不能只执行其中的一部分。 
    • 一致性(consistency):在事务处理执行前后,数据库是一致的(两个账户要么都变,或者都不变)。 
    • 隔离性(isolcation):一个事务处理对另一个事务处理没有影响。 
    • 持续性(durability):事务处理的效果能够被永久保存下来 。

    Jdbc的事务默认是打开的,也就是说执行每一步操作的话,事务都会隐式的进行提交,可以取消默认事务:

    connection.setAutoCommit(false);

    Demo

    package net.oseye.DbDemo;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    
    public class App 
    {
        public static void main( String[] args ) throws Exception
        {
            Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root","root");
            
            //取消默认事务
            conn.setAutoCommit(false);
            PreparedStatement pstmt=conn.prepareStatement("update user set user_name=? where user_id = ?");
            pstmt.setString(1, "kevin");
            pstmt.setInt(2, 1);
            pstmt.execute();
            
            pstmt=conn.prepareStatement("select user_name from user where user_id=?");
            pstmt.setInt(1,1);
            ResultSet rs=pstmt.executeQuery();
            //提交事务
            conn.commit();
            
            if(rs.next()){
            	System.out.println("用户名:"+rs.getString(1));
            }        
            rs.close();
            pstmt.close();
            conn.close();
        }
    }

    这里没有对事务做深入讨论,看到这儿容易让你产生误会,以为事务如此简单,请正式生产前一定找相关专业书籍学习,切记切记。

    JDBC的DataSource

    前文提到使用DriverManager获取数据库连接的低效,是因为数据库连接是一种关键的有限而昂贵的资源。所以JDK为我们提供了一个DataSource接口。DataSource作为 DriverManager 工具的替代项,DataSource 对象是获取连接的首选方法。DataSource接口:

    //尝试建立与此 DataSource 对象所表示的数据源的连接。 
    Connection getConnection() 
    
    //尝试建立与此 DataSource 对象所表示的数据源的连接。
    Connection getConnection(String username, String password) 

    有很多容器如Tomcat都实现DataSource接口而提供了连接池功能,现在我们来简单实现一个连接池:

    /**
     * 实现DataSource接口的简单连接池
     */
    public class ConnectPool implements DataSource {
    	private static final String url = "jdbc:mysql://127.0.0.1:3306/test";
    	private static final String user = "root";
    	private static final String pswd = "root";
    
    	// 连接池队列
    	private static LinkedList<Connection> pool = new LinkedList<Connection>();
    	private static ConnectPool instance = new ConnectPool();
    
    	/**
    	 * 获取数据源单例
    	 */
    	public static ConnectPool instance() {
    		if (instance == null)
    			instance = new ConnectPool();
    		return instance;
    	}
    
    	/**
    	 * 获取一个数据库连接
    	 */
    	public Connection getConnection() throws SQLException {
    		synchronized (pool) {
    			if (pool.size() > 0) {
    				return pool.removeFirst();
    			} else {
    				return DriverManager.getConnection(url, user, pswd);
    			}
    		}
    	}
    
    	/**
    	 * 连接归池,这里的实现思想是使用过的线程入池以备下次使用
    	 */
    	public static void freeConnection(Connection conn) {
    		pool.addLast(conn);
    	}
    
    	public Connection getConnection(String username, String password)
    			throws SQLException {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	public PrintWriter getLogWriter() throws SQLException {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	public void setLogWriter(PrintWriter out) throws SQLException {
    		// TODO Auto-generated method stub
    
    	}
    
    	public void setLoginTimeout(int seconds) throws SQLException {
    		// TODO Auto-generated method stub
    
    	}
    
    	public int getLoginTimeout() throws SQLException {
    		// TODO Auto-generated method stub
    		return 0;
    	}
    
    	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	public <T> T unwrap(Class<T> iface) throws SQLException {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	public boolean isWrapperFor(Class<?> iface) throws SQLException {
    		// TODO Auto-generated method stub
    		return false;
    	}
    }

    使用连接池

    public class App {
    	public static void main(String[] args) throws Exception {
    		//没有使用连接池
    		long start=System.currentTimeMillis();
    		for (int i = 0; i < 100; i++) {
    			Connection conn = DriverManager.getConnection(
    					"jdbc:mysql://127.0.0.1:3306/test",
    					"root", "root");
    
    			PreparedStatement pstmt = conn
    					.prepareStatement("insert into user(user_name) values(?)");
    			pstmt.setString(1, String.valueOf(System.currentTimeMillis()));
    			pstmt.execute();
    
    			pstmt.close();
    			conn.close();
    		}
    		System.out.println("没有使用连接池花费时间:"+(System.currentTimeMillis()-start));
    		//使用连接池
    		start=System.currentTimeMillis();
    		for (int i = 0; i < 100; i++) {
    			Connection conn=ConnectPool.instance().getConnection();
    			
    			PreparedStatement pstmt = conn
    					.prepareStatement("insert into user(user_name) values(?)");
    			pstmt.setString(1, String.valueOf(System.currentTimeMillis()));
    			pstmt.execute();
    
    			pstmt.close();
    			ConnectPool.freeConnection(conn);
    		}
    		System.out.println("使用连接池花费时间:"+(System.currentTimeMillis()-start));
    	}
    }

    输出

    没有使用连接池花费时间:4625
    使用连接池花费时间:2782

    示例是使用的单线程就能足以展现出差距了,如果使用多线程会更明显,但多线程就要对连接池进行同步操作,实现起来会更复杂。实例中的连接池实现虽然使用了同步(synchronized ),但是向当地弱,此实现只是简单展示连接池的实现以及使用,禁止把如此简单实现投入生产,切记切记。

    开源连接池

    有很多产品都实现了自己的连接池,如Jboss、Tomcat等容器,业界也有很多著名的开源连接池供大家使用,如:C3P0、BoneCP、DBCP 、Proxool等。我们以BoneCP连接池为例来展示。

    pom.xml增加BoneCP的依赖配置(BoneCP使用了SLF4J,这里暂时没做配置)

    <dependency>
    	<groupId>com.jolbox</groupId>
    	<artifactId>bonecp</artifactId>
    	<version>0.8.0.RELEASE</version>
    </dependency>

    App.java代码

    package net.oseye.DbDemo;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    
    import com.jolbox.bonecp.BoneCP;
    import com.jolbox.bonecp.BoneCPConfig;
    
    public class App {
    	public static void main(String[] args) {
    		long start=System.currentTimeMillis();
    		BoneCP connectionPool = null;
    		Connection connection = null;
    		try {
    			//连接池配置
    			BoneCPConfig config = new BoneCPConfig();
    			config.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/test");
    			config.setUsername("root");
    			config.setPassword("root");
    			config.setMinConnectionsPerPartition(5);
    			config.setMaxConnectionsPerPartition(10);
    			config.setPartitionCount(1);
    
    			connectionPool = new BoneCP(config);
    			for (int i = 0; i < 100; i++) {
    				//获取链接
    				connection = connectionPool.getConnection();
    				if (connection != null) {
    					PreparedStatement pstmt = connection
    							.prepareStatement("insert into user(user_name) values(?)");
    					pstmt.setString(1,
    							String.valueOf(System.currentTimeMillis()));
    					pstmt.execute();
    					pstmt.close();
    					connection.close();
    				}
    			}
    			connectionPool.shutdown();
    			System.out.println("使用BoneCP连接池花费时间:"+(System.currentTimeMillis()-start));
    		} catch (SQLException e) {
    			e.printStackTrace();
    		}
    	}
    }

    输出

    使用BoneCP连接池花费时间:3531

    出处:http://www.zhaiqianfeng.com    
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    saltstack推送文件到节点
    Linux查看僵尸进程
    Linux批量对某个目录下特定文件进行修改内容
    Linux查看网络连接数
    Linux查看当前目录下哪个目录占用容量最多
    Linux查看inodes最多的目录
    Linux用命令过滤出ip地址
    mysql表字段的增删改操作
    Python安装模块超时
    sql 查询结果中加入空值列
  • 原文地址:https://www.cnblogs.com/zhaiqianfeng/p/4620187.html
Copyright © 2020-2023  润新知