精华内容
下载资源
问答
  • * 数据库连接池特点: * 获取连接时不需要了解连接的名字,连接池内部维护连接的名字 * 支持多线程,保证获取到的连接一定是没有被其他线程正在使用 * 按需创建连接,可以创建多个连接,可以控制连接的数量 * 连接...
  • 主要介绍了spring boot配置MySQL数据库连接、Hikari连接池和Mybatis的简单配置方法,需要的朋友可以参考下
  • Java建立数据库连接

    2020-12-22 19:31:37
    把该模式应用到数据库连接管理领域,是建立一个数据库连接池,提供一套高效的连接分配、使用策略,终目标是实现连接的高效、安全的复用。  数据库连接池的基本原理是在内部对象池中维护一定数量的数据库连接,并...
  • 本文实例分析了ThinkPHP框架分布式数据库连接方法。分享给大家供大家参考,具体如下: Thinkphp作为国内的一款流行框架,相信使用的人一定不在少数。本篇我们来分析一下Thinkphp中比较重要的一部分——分布式数据库...
  • 一个简单的java项目,eclipse直接导入,里面有demo可以直接运行连接数据库运行操作
  • 参数化配置数据库连接,以及简单shell如何传参调度Kettle
  • 主要介绍了nodejs基于mssql模块连接sqlserver数据库简单封装操作,结合实例形式分析了nodejs中mssql模块的安装与操作sqlserver数据库相关使用技巧,需要的朋友可以参考下
  • Navicat是一套快速、可靠并价格相当便宜的数据库管理工具,专为简化数据库的管理及降低系统管理成本而设。...Navicat 是以直觉化的图形用户界面而建的,让你可以以安全并且简单的方式创建、组织、访问并共用信息。
  • 主要给大家介绍了关于thinkphp3.2同时连接两个数据库简单方法,文中通过示例代码介绍的非常详细,对大家学习或者使用thinkphp3.2具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
  • #连接数据库测试 import pymysql #打开数据库 db = pymysql.connect(host="localhost",user="root",password="root",db="test") #使用cursor()方法获取操作游标 cur = db.cursor() #查询操作 sql = "select * from ...
  • 数据库连接工具Navicat

    2020-09-18 17:36:47
    Navicat是连接数据库的工具,可以连接mysql数据库,能够同时连接管理多个mysql数据库。友好的图形操作界面,简单的操作就能够连接上本地和云端的mysql服务器。
  • 连接MySQL数据库之前,您必须指定以下信息: MySQL数据源名称或DSN:指定MySQL数据库服务器的地址。您可以使用IP地址或服务器名称,例如,127.0.0.1 或 localhost MySQL数据库名称:表示要连接数据库的名称。 ...
  • 主要为大家介绍了Java实现数据库连接简易教程,感兴趣的小伙伴们可以参考一下
  • 数据库连接

    千次阅读 多人点赞 2019-10-22 19:54:01
    1、什么是数据库连接池? 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个; 释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放...

    1、什么是数据库连接池?

    数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;

    释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。

    这项技术能明显提高对数据库操作的性能

    2、连接池解决的问题???

          不断的创建、销毁链接,会导致访问数据库服务器的压力,而且对于内存来说,不断的开辟与销毁,内存的使用率极低。使用数据库连接池可以明显提高对数据库操作的性能!

          数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并将这些连接组成一个连接池(简单说:在一个“池”里放了好多半成品的数据库连接对象),由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数

    3、数据库连接池的工作原理???

      1、三步骤     连接池的建立,连接池中连接的使用管理,连接池的关闭

    一、连接池的建立:创建几个连接对象,以便使用的时候能从连接池中获取

    二、客户请求数据库连接时,首先查看连接池中是否有空闲连接,若存在,将连接分配给用户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大的连接数,如果没有达到,就重新创建一个连接给请求的客户,如果达到就按照设定的最大等待时间进行等待,超出的最大等待的时间,就抛出异常给用户,当用户释放数据连接的时候,先判断该连接的引用次数是否超过了规定的值,如果超过就删除,如果没有的话,就保留为其他客户服务。保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源的资源开销。

    三、连接池的关闭,当应用程序退出的时候i,关闭所有的连接,释放相关的资源,正好与创建相反

    4、数据库连接配置时的参数(可选,有默认值)

    最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费.

    最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作

    最大空闲时间:连接池对象最长的闲置时间,如果长时间没有被使用,那么此连接对象就会被销毁。

    获取连接超时时间:池子中总共有50条连接,当这50条连接都处于工作状态时,第51个连接请求到达,让其加入到等待队列中,等待时间如果超过了设置的超时时间,则抛出异常

    初始化连接数:当数据库连接池创建时,初始化的连接数量

    5、主流的数据库连接池技术

    创建QueryRunner时,可以传递一个数据源DataSource,我们可以将这个数据源理解为数据库连接池,那么我们现在使用不同的数据库连接池技术,目的就只有一个,得到数据源对象

    6、DBCP

    介绍:DBCP(DataBase Connection Pool)属于Apache顶级项目Commons中的核心子项目(最早在Jakarta Commons里就有),它内部依赖于Commons中的另一个子项目Pool,连接池最核心的“池”,就是由Pool组件提供的,因此,DBCP的性能实际上就是Pool的性能

    步骤:

    导包 build path

    书写dbcp.properties属性文件,文件目录在src下

    书写dbcp工具类,来提供数据源

    测试数据源是否可用

    7、封装一个工具类,专门用于向外界提供dbcp的数据源。

    /**
     * 此类的作用就是生成并且提供dbcp的数据源
     * @author Administrator
     *
     */
    public class DBCPUtils {
    	private static DataSource dataSource = null;
    	
    	//完成对数据源的赋值,赋值只需要走一次,所以在静态代码块中完成赋值
    	static {
    		Properties properties = new Properties();
    		FileInputStream fStream = null;
    		try {
    			//创建输入流对象,来读取配置文件dbcp.properties
    			fStream = new FileInputStream("src/dbcp.properties");
    			//将流中读取到的内容加载属性文件对象中
    			properties.load(fStream);
    			//将加载完毕内容的属性文件对象传递到dbcp创建数据源的方法中,生成数据源
    			dataSource = BasicDataSourceFactory.createDataSource(properties);
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    	
    	//返回数据源的方法
    	public static DataSource getDataSource() {
    		return dataSource;
    	}
    }

    测试代码的书写:

    public static void main(String[] args) throws SQLException {
    		/*
    		 	使用dbcp数据源来操作数据库
    		 	方式1:在创建QueryRunner对象,给定数据源,此时不需要书写连接获取的代码,会自动从给定的数据源中获取连接
    		 	使用完毕,不需要执行关闭资源的方法,连接对象会自动归还池子
    		 	
    		 	方式2:我们可以通过数据源的getConnection()获取到由池子管理的数据库连接对象
    		 	使用完毕后,执行关闭资源的方法,并不是将连接对象销毁,而是将连接对象归还池子
    		 */
    		/*
    		QueryRunner qRunner = new QueryRunner(DBCPUtils.getDataSource());
    		List<User> users = qRunner.query("select * from user", 
    				new BeanListHandler<User>(User.class));
    		for (User user : users) {
    			System.out.println(user);
    		}*/
    		
    		DataSource dataSource = DBCPUtils.getDataSource();
    		Connection connection = dataSource.getConnection();
    		QueryRunner qRunner = new QueryRunner();
    		List<User> users = qRunner.query(connection, "select * from user", 
    				new BeanListHandler<User>(User.class));
    		for (User user : users) {
    			System.out.println(user);
    		}
    		DbUtils.close(connection);
    	}

    写一个参数文件dbcp.properties:

    //创建输入流对象,来读取配置文件dbcp.properties

    7、druid的使用

    介绍:它除了提供性能卓越的连接池功能外,还集成了SQL监控,黑名单拦截等功能,用它自己的话说,Druid是“为监控而生”。借助于阿里这个平台的号召力,产品一经发布就赢得了大批用户的拥趸,从用户使用的反馈来看,Druid也确实没让用户失望。

    使用步骤:

    导包   

    书写配置文件druid.properties    

    书写druid工具类,来生成并提供druid的数据源   

    /**
     * 此类专门用于生成druid数据源
     * @author Administrator
     *
     */
    public class DruidUtils {
    	//声明数据源
    	private static DataSource dataSource = null;
    	
    	//为数据源赋值
    	static {
    		Properties properties = new Properties();
    		FileInputStream fStream = null;
    		try {
    			fStream = new FileInputStream("src/druid.properties");
    			properties.load(fStream);
    			dataSource = DruidDataSourceFactory.createDataSource(properties);
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		} finally {
    			try {
    				if (fStream != null) {
    					fStream.close();
    				}
    			} catch (IOException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    	}
    	
    	//返回获取数据源的方法
    	public static DataSource getDataSource() {
    		return dataSource;
    	}
    }

    书写测试代码

    public static void main(String[] args) {
    		//使用druid+dbutils完成添加操作
    		QueryRunner qRunner = new QueryRunner(DruidUtils.getDataSource());
    		try {
    			int row = qRunner.update("insert into user values (null, ?, ?, ?)", 
    								"小阔爱", "666", "男");
    			System.out.println(row > 0 ? "添加成功" : "添加失败");
    		} catch (SQLException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}

    8、c3p0的使用

    介绍:C3P0是我使用的第一款数据库连接池,在很长一段时间内,它一直是Java领域内数据库连接池的代名词,当年盛极一时的Hibernate都将其作为内置的数据库连接池,可以业内对它的稳定性还是认可的。C3P0功能简单易用,稳定性好这是它的优点,但是性能上的缺点却让它彻底被打入冷宫。C3P0的性能很差,差到即便是同时代的产品相比它也是垫底的,更不用和Druid、HikariCP等相比了。正常来讲,有问题很正常,改就是了,但c3p0最致命的问题就是架构设计过于复杂,让重构变成了一项不可能完成的任务。随着国内互联网大潮的涌起,性能有硬伤的c3p0彻底的退出了历史舞台。

    使用步骤:

    导包   

    书写c3p0.properties文件,一定要放在src下   

    如果c3p0的配置文件名称叫c3p0.properties,并且放在了src下,那么就会自动读取配置文件!

    测试代码  

    测试代码:
    public class C3P0Demo {
    	private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
    	
    	public static void main(String[] args) {
    		QueryRunner qRunner = new QueryRunner(dataSource);
    		try {
    			User user = qRunner.query("select * from user where uid = ?",
    					new BeanHandler<User>(User.class), 2);
    			System.out.println(user);
    		} catch (SQLException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();

    五,duid,dbcp,c3p0 的区别。

     

     

     

     

     

     

     

    展开全文
  • C#简单数据库连接的登录界面 简单 适合初学者 数据库连接部分 SqlConnection con = new SqlConnection("Data Source=.;Initial Catalog=chaoshi;Integrated Security=True"); con.Open(); String sql = ...
  • 实现redis与springmvc配置,数据库连接简单实现,待完善实现连接池
  • Android中的数据库连接

    千次阅读 2019-11-02 20:19:14
    一个数据库连接能实现并发么?要是一个数据库链接可以实现并发,那么为什么需要数据库连接池? 数据库连接池介绍 每次提到连接池我们很快能想到线程池。线程池的创建可以减少了创建和销毁线程的次数,每个工作线程都...

    在这里插入图片描述
    最近在看数据库相关的三方库的时候,我发现在Android应用开发的时候是可以并行操作数据库的读写,但Android默认的数据连接池中只有一个数据库链接。一个数据库连接能实现并发么?要是一个数据库链接可以实现并发,那么为什么需要数据库连接池?

    数据库连接池介绍

    每次提到连接池我们很快能想到线程池。线程池的创建可以减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

    数据库连接是一种关键的有限的昂贵的资源,对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,减少链接不断传销和销毁带来的资源浪费。

    数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

    数据库连接池的最小连接数和最大连接数的设置要考虑到以下几个因素:

    1. 最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费。

    2. 最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作

    3. 如果最小连接数与最大连接数相差很大:那么最先连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接。不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,他将被放到连接池中等待重复使用或是空间超时后被释放。

    Android数据库相关类介绍

    • SQLiteOpenHelper:管理SQLite的帮助类,提供获取SQLIteDatabase实例的方法,它会在第一次使用数据库时调用获取实例方法时创建SQLiteDatabase实例,并且处理数据库版本变化,开发人员在实现ContentProvider时都要实现一个自定义的SQLiteOpenHelper类,处理数据的创建、升级和降级。
    • SQLiteDatabase:代表一个打开的SQLite数据库,提供了执行数据库操作的接口方法。如果不需要在进程之间共享数据,应用程序也可以自行创建这个类的实例来读写SQLite数据库。
    • SQLiteSession:SQLiteSession负责管理数据库连接和事务的生命周期,通过SQLiteConnectionPool获取数据库连接来执行具体的数据库操作。
    • SQLiteConnectionPool:数据库连接池,管理所有打开的数据库连接(Connection)。所有数据库连接都是通过它来打开,打开后会加入连接池,在读写数据库时需要从连接池中获取一个数据库连接来使用。
    • SQLiteConnection:代表了数据库连接,每个Connection封装了一个native层的sqlite3实例,通过JNI调用SQLite动态库的接口方法操作数据库,Connection要么被Session持有,要么被连接池持有。
    • CursorFactory:可选的Cursor工厂,可以提供自定义工厂来创建Cursor。
    • DatabaseErrorHandler:可选的数据库异常处理器(目前仅处理数据库Corruption),如果不提供,将会使用默认的异常处理器。
    • SQLiteDatabaseConfiguration:数据库配置,应用程序可以创建多个到SQLite数据库的连接,这个类用来保证每个连接的配置都是相同的。
    • SQLiteQuery和SQLiteStatement:从抽象类SQLiteProgram派生,封装了SQL语句的执行过程,在执行时自动组装待执行的SQL语句,并调用SQLiteSession来执行数据库操作。这两个类的实现应用了设计模式中的命令模式。

    每个类的更加详细的介绍可以阅读 SQLite数据库学习小结**——Frameworks**层实现 这篇文章,我们这里主要学习 SQLiteConnectionPool 相关的知识。

    SQLiteConnectionPool

    数据库连接池我们先看一下它的大小,每个链接的获取以及其他功能?

    连接池大小

    目前Android系统的实现中,如果以非WAL模式打开数据库,连接池中只会保持一个数据库连接,如果以WAL模式打开数据库,连接池中的最大连接数量则根据系统配置决定,默认配置是两个。

    //SQLiteConnectionPool.java
    private SQLiteConnectionPool(SQLiteDatabaseConfiguration configuration) {
        //数据库的配置信息
        mConfiguration = new SQLiteDatabaseConfiguration(configuration);
        //设置最大的数据库链接个数
        setMaxConnectionPoolSizeLocked();
        //超时处理句柄设置,如果超时时间为MAX_VALUE,那么链接永远不关闭
        // If timeout is set, setup idle connection handler
        // In case of MAX_VALUE - idle connections are never closed
        if (mConfiguration.idleConnectionTimeoutMs != Long.MAX_VALUE) {
            setupIdleConnectionHandler(Looper.getMainLooper(),
                    mConfiguration.idleConnectionTimeoutMs);
        }
    }
    private void setMaxConnectionPoolSizeLocked() {
        if (!mConfiguration.isInMemoryDb()
                && (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0){
            //获取debug.sqlite.wal.poolsize配置的大小,默认值是com.android.internal.R.integer.db_connection_pool_size
            //最小值为2
            mMaxConnectionPoolSize = SQLiteGlobal.getWALConnectionPoolSize();
        } else {
            // We don't actually need to always restrict the connection pool size to 1
            // for non-WAL databases.  There might be reasons to use connection pooling
            // with other journal modes. However, we should always keep pool size of 1 for in-memory
            // databases since every :memory: db is separate from another.
            // For now, enabling connection pooling and using WAL are the same thing in the API.
            //内存数据库和非WAL数据库时数据库连接池大小为1
            mMaxConnectionPoolSize = 1;
        }
    }
    

    虽然名为连接池,但是从源码来看,目前实现的池中只有一个数据库连接(以后的Android版本可能会扩展),所以如果应用程序中有大量的并发数据库读和写操作的话,每个操作的时长都可能受到影响,所以数据库操作应放在工作线程中执行,以免影响UI响应。

    这里有人可能产生疑问,我在进行Android应用开发的时候是可以并行操作数据库的读写,一个数据库连接能实现并发么?要是一个数据库链接可以实现并发,那么为什么需要数据库连接池?

    这里说一下我自己的理解:一个数据库链接是一个Socket 通道,当这个Connection 被其它 Session占用的时候后续的Session 的操作必须等待这个 Connection 被释放,所以数据库的 Connection 的工作其实是串行的,这个在 MySqlOracle 的文档中也能找到描述。所以在Android中默认的数据库连接池只有一个数据库链接的时候,所有在这个数据库上的操作都是串行的。我们平时在多线程中的数据库操作都是串行的。

    这些将会下下面代码分析的过程中一一体现出来_

    数据库链接池的构造

    这里主要讲数据库连接池的创建和池中的第一条链接的产生。

    //SQLiteConnectionPool.java
    public final class SQLiteConnectionPool implements Closeable {
        private static final String TAG = "SQLiteConnectionPool";
        // Amount of time to wait in milliseconds before unblocking acquireConnection
        // and logging a message about the connection pool being busy.
        private static final long CONNECTION_POOL_BUSY_MILLIS = 30 * 1000; // 30 seconds
        private final CloseGuard mCloseGuard = CloseGuard.get();
        private final Object mLock = new Object();
        private final AtomicBoolean mConnectionLeaked = new AtomicBoolean();
        //数据库的配置信息
        private final SQLiteDatabaseConfiguration mConfiguration;
        //数据库连接池的最大链接数
        private int mMaxConnectionPoolSize;
        //数据库是否打开
        private boolean mIsOpen;
        //创建的链接id
        private int mNextConnectionId;
        //连接等待池其实是由等待的连接组成的链
        private ConnectionWaiter mConnectionWaiterPool;
        //连接等待队列
        private ConnectionWaiter mConnectionWaiterQueue;
        //非主链接的引用,强引用需要主动回收
        // Strong references to all available connections.
        private final ArrayList<SQLiteConnection> mAvailableNonPrimaryConnections =
                new ArrayList<SQLiteConnection>();
        //主链接
        private SQLiteConnection mAvailablePrimaryConnection;
        /***部分代码省略***/
    }
    

    打开数据库

    当我们在进行 SQLiteOpenHelper.getWritableDatabaseSQLiteOpenHelper.getReadableDatabase 的时候如果数据库没有打开那么会打开数据库,打开数据库也就是创建数据库链接。

    //SQLiteDatabase.java
    private static SQLiteDatabase openDatabase(@NonNull String path,
            @NonNull OpenParams openParams) {
        Preconditions.checkArgument(openParams != null, "OpenParams cannot be null");
        SQLiteDatabase db = new SQLiteDatabase(path, openParams.mOpenFlags,
                openParams.mCursorFactory, openParams.mErrorHandler,
                openParams.mLookasideSlotSize, openParams.mLookasideSlotCount,
                openParams.mIdleConnectionTimeout, openParams.mJournalMode, openParams.mSyncMode);
        //内部调用openInner
        db.open();
        return db;
    }
    //打开数据库
    private void openInner() {
        synchronized (mLock) {
            assert mConnectionPoolLocked == null;
            mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
            mCloseGuardLocked.open("close");
        }
        synchronized (sActiveDatabases) {
            sActiveDatabases.put(this, null);
        }
    }
    

    在这里我们看到了SQLiteConnectionPoll 的调用,这里由数据库链接池创建数据库链接从而打开数据库。

    //SQLiteConnectionPool.java
    public static SQLiteConnectionPool open(SQLiteDatabaseConfiguration configuration) {
        //校验数据库信息
        if (configuration == null) {
            throw new IllegalArgumentException("configuration must not be null.");
        }
        // Create the pool.创建连接池
        SQLiteConnectionPool pool = new SQLiteConnectionPool(configuration);
        pool.open(); // might throw
        return pool;
    }
    // Might throw,打开数据库
    private void open() {
        // Open the primary connection.
        // This might throw if the database is corrupt.
        //获取主链接并打开数据库,如果数据库损坏可能抛出异常
        mAvailablePrimaryConnection = openConnectionLocked(mConfiguration,
                true /*primaryConnection*/); // might throw
        // Mark it released so it can be closed after idle timeout
        //释放当前链接,以便于被关闭或者被超时回收
        synchronized (mLock) {
            if (mIdleConnectionHandler != null) {
                mIdleConnectionHandler.connectionReleased(mAvailablePrimaryConnection);
            }
        }
        // Mark the pool as being open for business.
        mIsOpen = true;
        mCloseGuard.open("close");
    }
    // Might throw.
    private SQLiteConnection openConnectionLocked(SQLiteDatabaseConfiguration configuration,
        boolean primaryConnection) {
        //connectionId作为链接id,每次新创建一个数据库链接id自增1
        final int connectionId = mNextConnectionId++;
        return SQLiteConnection.open(this, configuration,connectionId, primaryConnection); // might throw
    }
    //SQLiteConnection.java
    // Called by SQLiteConnectionPool only.
    static SQLiteConnection open(SQLiteConnectionPool pool, SQLiteDatabaseConfiguration configuration,
        int connectionId, boolean primaryConnection) {
        SQLiteConnection connection = new SQLiteConnection(pool, configuration,connectionId, primaryConnection);
        try {
            //建立数据库链接
            connection.open();
            return connection;
        } catch (SQLiteException ex) {
            connection.dispose(false);
            throw ex;
        }
    }
    

    在这里我们能看到数据库连接池第一条链接的创建是在打开数据库的时候。

    创建数据库链接

    除过在打开数据的时候创建数据库链接,我们还会在一下情况下可能创建数据库链接。

    • 创建数据库,调用open
    • 重新加载数据库配置,调用reconfigure
    • 创建主链接,调用tryAcquirePrimaryConnectionLocked
    • 创建非主链接,调用tryAcquireNonPrimaryConnectionLocked

    这四种情况我们都可能会调用 SQLiteConnectionPool.openConnectionLocked 创建数据库链接,其中创建数据库重新加载数据库配置都是创建的主链接

    数据库链接的使用

    在这之前我们先回想 ConnectionSession 的概念:

    连接(Connection):连接是从客户端到ORACLE实例的一条物理路径。连接可以在网络上建立,或者在本机通过IPC机制建立。通常会在客户端进程与一个专用服务器或一个调度器之间建立连接。

    会话(Session) 是和连接(Connection)是同时建立的,两者是对同一件事情不同层次的描述。简单讲,连接(Connection)是物理上的客户端同服务器的通信链路,会话(Session)是逻辑上的用户同服务器的通信交互。

    我们一般往数据库插入一条数据:

    //创建数据库的help
    OpenHelper openHelper = new OpenHelper(getApplicationContext(), "demo", null, 1);
    //打开数据库标识写操作
    SQLiteDatabase writableDatabase = tanzhenxing.getWritableDatabase();
    ContentValues contentValues = new ContentValues();
    contentValues.put("TIME", System.currentTimeMillis());
    //向SYSTEM_MSG表插入一条数据,TIME的当时间戳
    writableDatabase.insert("SYSTEM_MSG", null, contentValues);
    

    SQliteDatabase的内部调用:

    //SQLiteDatabase.java
    public long insert(String table, String nullColumnHack, ContentValues values) {
        try {
            //内部封装SQLiteStatement,调用statement.executeInsert();
            return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
        } catch (SQLException e) {
            Log.e(TAG, "Error inserting " + values, e);
            return -1;
        }
    }
    //SQLiteStatement.java
    public long executeInsert() {
        acquireReference();
        try {
            return getSession().executeForLastInsertedRowId(
                    getSql(), getBindArgs(), getConnectionFlags(), null);
        } catch (SQLiteDatabaseCorruptException ex) {
            onCorruption();
            throw ex;
        } finally {
            releaseReference();
        }
    }
    //SQLiteStatement.java的父类SQLiteProgram的方法
    protected final SQLiteSession getSession() {
        return mDatabase.getThreadSession();
    }
    

    在这里我们能看到最终的执行是有 Session 进行操作的。

    //SQLiteDatabase.java
    // Thread-local for database sessions that belong to this database.
    // Each thread has its own database session.
    // INVARIANT: Immutable.
    //属于当前数据库的会话,每个线程都有一会话,不可变。
    private final ThreadLocal<SQLiteSession> mThreadSession = ThreadLocal
            .withInitial(this::createSession);
    SQLiteSession createSession() {
        final SQLiteConnectionPool pool;
        synchronized (mLock) {
            throwIfNotOpenLocked();
            pool = mConnectionPoolLocked;
        }
        return new SQLiteSession(pool);
    }
    SQLiteSession getThreadSession() {
        return mThreadSession.get(); // initialValue() throws if database closed
    }
    

    好了扯了这么久到底什么时候使用 Connection 呢?

    //SQLiteSession.java
    public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags,
            CancellationSignal cancellationSignal) {
        //校验sql
        if (sql == null) {
            throw new IllegalArgumentException("sql must not be null.");
        }
        //对某些SQL语句(例如“ BEGIN”," COMMIT”和“ ROLLBACK”)执行特殊的重新解释,以确保事务状态不变式为保持。 
        if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
            return 0;
        }
        //获取数据库链接
        acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
        try {
            //使用数据库链接进行数据库操作
            return mConnection.executeForLastInsertedRowId(sql, bindArgs,
                    cancellationSignal); // might throw
        } finally {
            //释放数据库链接
            releaseConnection(); // might throw
        }
    }
    //从数据库连接池中获取链接
    private void acquireConnection(String sql, int connectionFlags,
            CancellationSignal cancellationSignal) {
        if (mConnection == null) {
            assert mConnectionUseCount == 0;
            mConnection = mConnectionPool.acquireConnection(sql, connectionFlags,
                    cancellationSignal); // might throw
            mConnectionFlags = connectionFlags;
        }
        mConnectionUseCount += 1;
    }
    

    我们总结一下上述内容:我们进行数据库操作的时候每次操作都使用的Session ,多个线程执行数据库操作会有多个SessionSession 的内部操作调用的是ConnectionConnection 是从数据库连接池中获取的。

    如果数据库连接池有多个数据库链接,那么数据库的殂谢操作可以并发,否则只能串行操作。

    从连接池中获取数据库链接

    //SQLiteConnectionPool.java
    public SQLiteConnection acquireConnection(String sql, int connectionFlags,
            CancellationSignal cancellationSignal) {
        SQLiteConnection con = waitForConnection(sql, connectionFlags, cancellationSignal);
        synchronized (mLock) {
            if (mIdleConnectionHandler != null) {
                mIdleConnectionHandler.connectionAcquired(con);
            }
        }
        return con;
    }
    

    从上面的 waitForConnection 方法的名字我们可以猜测这个方法可能产生阻塞。

    //SQLiteConnectionPool.java
    // Might throw.
    private SQLiteConnection waitForConnection(String sql, int connectionFlags,
            CancellationSignal cancellationSignal) {
        //是否需要主链接
        final boolean wantPrimaryConnection =
                (connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0;
        final ConnectionWaiter waiter;
        final int nonce;
        synchronized (mLock) {
            throwIfClosedLocked();//如果数据库关闭,那么抛出异常
            // Abort if canceled.
            //如果取消信号的回调不为空,那么执行回调检测是否需要取消
            if (cancellationSignal != null) {
                cancellationSignal.throwIfCanceled();
            }
            // Try to acquire a connection.
            //尝试获得一个数据库链接
            SQLiteConnection connection = null;
            //如果不需要主链接,那么尝试获取非主链接
            if (!wantPrimaryConnection) {
                connection = tryAcquireNonPrimaryConnectionLocked(
                        sql, connectionFlags); // might throw
            }
            //如果获取不到非链接,那么尝试获取主链接
            if (connection == null) {
                connection = tryAcquirePrimaryConnectionLocked(connectionFlags); // might throw
            }
            if (connection != null) {
                return connection;
            }
            // No connections available.  Enqueue a waiter in priority order.
            //没有可用的连接。按优先级排队服务员。
            final int priority = getPriority(connectionFlags);
            final long startTime = SystemClock.uptimeMillis();
            //创建一个等待获取链接的对象
            waiter = obtainConnectionWaiterLocked(Thread.currentThread(), startTime,
                    priority, wantPrimaryConnection, sql, connectionFlags);
            ConnectionWaiter predecessor = null;
            ConnectionWaiter successor = mConnectionWaiterQueue;
            //按照优先级查找插入的位置
            while (successor != null) {
                if (priority > successor.mPriority) {
                    waiter.mNext = successor;
                    break;
                }
                predecessor = successor;
                successor = successor.mNext;
            }
            //插入等待队列
            if (predecessor != null) {
                predecessor.mNext = waiter;
            } else {
                mConnectionWaiterQueue = waiter;
            }
            nonce = waiter.mNonce;
        }
        // Set up the cancellation listener.
        //设置取消监听器,在等待的过程中如果取消等待那么执行cancelConnectionWaiterLocked
        if (cancellationSignal != null) {
            cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
                @Override
                public void onCancel() {
                    synchronized (mLock) {
                        if (waiter.mNonce == nonce) {
                            //从等待队列中删除这个节点数据
                            //给waiter添加OperationCanceledException异常信息
                            //唤醒waiter对应线程的阻塞
                            //调用wakeConnectionWaitersLocked判断队列其他waiter是否状态有更新
                            cancelConnectionWaiterLocked(waiter);
                        }
                    }
                }
            });
        }
        try {
            // Park the thread until a connection is assigned or the pool is closed.
            // Rethrow an exception from the wait, if we got one.
            //驻留线程,直到分配了连接或关闭了池。
            //如果有异常,则从等待中抛出异常。 
            long busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
            long nextBusyTimeoutTime = waiter.mStartTime + busyTimeoutMillis;
            for (;;) {
                // Detect and recover from connection leaks.
                //是否需要从泄露中进行恢复,之前被调用onConnectionLeaked
                if (mConnectionLeaked.compareAndSet(true, false)) {
                    synchronized (mLock) {
                        //为等待数据库链接队列进行链接赋值
                        wakeConnectionWaitersLocked();
                    }
                }
                // Wait to be unparked (may already have happened), a timeout, or interruption.
                //阻塞busyTimeoutMillis毫秒,或者中间被执行LockSupport.unpark
                //被执行cancelConnectionWaiterLocked进行取消
                //或者被执行wakeConnectionWaitersLocked进行链接分配
                LockSupport.parkNanos(this, busyTimeoutMillis * 1000000L);
                // Clear the interrupted flag, just in case.
                Thread.interrupted();//重置当前线程的中断状态
                // Check whether we are done waiting yet.
                //检查我们是否已经完成等待。
                synchronized (mLock) {
                    throwIfClosedLocked();//如果数据库关闭,那么抛出异常
                    final SQLiteConnection connection = waiter.mAssignedConnection;
                    final RuntimeException ex = waiter.mException;
                    //如果已经分配链接,或者发送异常
                    if (connection != null || ex != null) {
                        recycleConnectionWaiterLocked(waiter);//回收waiter
                        if (connection != null) {//返回分配链接
                            return connection;
                        }
                        throw ex; // rethrow!重新抛出异常
                    }
    
                    final long now = SystemClock.uptimeMillis();
                    if (now < nextBusyTimeoutTime) {
                        //parkNanos阻塞时间不够busyTimeoutMillis毫秒,被执行LockSupport.unpark
                        busyTimeoutMillis = now - nextBusyTimeoutTime;
                    } else {
                        //输出日志
                        logConnectionPoolBusyLocked(now - waiter.mStartTime, connectionFlags);
                        //重置下次阻塞时间
                        busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
                        nextBusyTimeoutTime = now + busyTimeoutMillis;
                    }
                }
            }
        } finally {
            // Remove the cancellation listener.
            //有异常,或者获取等到了分配的链接那么解绑回调信息
            if (cancellationSignal != null) {
                cancellationSignal.setOnCancelListener(null);
            }
        }
    }
    

    这里利用LockSupport.parkNanos 循环判断是否获得了数据库链接否则继续睡眠,直到这次操作被取消或者获得数据库链接。

    主链接的获取

    //SQLiteConnectionPool.java
    // Might throw.
    @GuardedBy("mLock")
    private SQLiteConnection tryAcquirePrimaryConnectionLocked(int connectionFlags) {
        // If the primary connection is available, acquire it now.
        //如果主要连接可用,请立即获取。
        SQLiteConnection connection = mAvailablePrimaryConnection;
        if (connection != null) {
            mAvailablePrimaryConnection = null;
            finishAcquireConnectionLocked(connection, connectionFlags); // might throw
            return connection;
        }
        // Make sure that the primary connection actually exists and has just been acquired.
        //确保主要连接确实存在并且刚刚被获取。
        for (SQLiteConnection acquiredConnection : mAcquiredConnections.keySet()) {
            if (acquiredConnection.isPrimaryConnection()) {
                return null;
            }
        }
        // Uhoh.  No primary connection!  Either this is the first time we asked
        // for it, or maybe it leaked?
        //第一次创建数据库主链接,或者主链接被回收
        connection = openConnectionLocked(mConfiguration,
                true /*primaryConnection*/); // might throw
        finishAcquireConnectionLocked(connection, connectionFlags); // might throw
        return connection;
    }
    

    我们可以看到主数据库链接只会有一个,如果被占用那么需要等待,如果没有那么就需要创建。

    获取非主链接

    //SQLiteConnectionPool.java
    // Might throw.
    private SQLiteConnection tryAcquireNonPrimaryConnectionLocked(
            String sql, int connectionFlags) {
        // Try to acquire the next connection in the queue.
        SQLiteConnection connection;
        //尝试获取队列中的下一个连接。
        final int availableCount = mAvailableNonPrimaryConnections.size();
        if (availableCount > 1 && sql != null) {
            // If we have a choice, then prefer a connection that has the
            // prepared statement in its cache.
            //检查我们是否可以在其缓存中选择具有prepare语句的连接。
            for (int i = 0; i < availableCount; i++) {
                connection = mAvailableNonPrimaryConnections.get(i);
                if (connection.isPreparedStatementInCache(sql)) {
                    mAvailableNonPrimaryConnections.remove(i);
                    finishAcquireConnectionLocked(connection, connectionFlags); // might throw
                    return connection;
                }
            }
        }
        //否则获取可以非用连接队列中的最后一个链接
        if (availableCount > 0) {
            // Otherwise, just grab the next one.
            connection = mAvailableNonPrimaryConnections.remove(availableCount - 1);
            finishAcquireConnectionLocked(connection, connectionFlags); // might throw
            return connection;
        }
        //如果没有可以的非主链接,那么就需要扩展数据库连接池
        // Expand the pool if needed.
        int openConnections = mAcquiredConnections.size();
        if (mAvailablePrimaryConnection != null) {
            openConnections += 1;
        }
        //如果数据库连接池已经达到上限那么,返回null
        if (openConnections >= mMaxConnectionPoolSize) {
            return null;
        }
        //否则创建新的非主链接
        connection = openConnectionLocked(mConfiguration,
                false /*primaryConnection*/); // might throw
        finishAcquireConnectionLocked(connection, connectionFlags); // might throw
        return connection;
    }
    

    非主数据库链接数量的多少受限于数据库连接池的大小。

    数据库链接释放

    有创建获取就会有释放回收。

    //SQLiteConnectionPool.java
    //释放数据库链接返回连接池
    public void releaseConnection(SQLiteConnection connection) {
        synchronized (mLock) {
            //idle链接句柄事件处理connectionReleased
            if (mIdleConnectionHandler != null) {
                mIdleConnectionHandler.connectionReleased(connection);
            }
            //获取这个链接的状态
            //NORMAL,正常返回连接池
            //RECONFIGURE,必须先重新配置连接,然后才能返回。
            //DISCARD,连接必须关闭并丢弃。
            AcquiredConnectionStatus status = mAcquiredConnections.remove(connection);
            if (status == null) {
                throw new IllegalStateException("Cannot perform this operation "
                        + "because the specified connection was not acquired "
                        + "from this pool or has already been released.");
            }
            //简历是否已经关闭连接池
            if (!mIsOpen) {
                closeConnectionAndLogExceptionsLocked(connection);
            } else if (connection.isPrimaryConnection()) {//如果是主链接
                //判断这个数据库链接是否需要回收
                if (recycleConnectionLocked(connection, status)) {
                    assert mAvailablePrimaryConnection == null;
                    mAvailablePrimaryConnection = connection;//标识主链接可用,被占用的时候为null
                }
                判断队列其他waiter是否状态有更新
                wakeConnectionWaitersLocked();
            } else if (mAvailableNonPrimaryConnections.size() >= mMaxConnectionPoolSize - 1) {
                //可用的非主链接数+主链接大于或等于数据库连接池的最大链接数的时候关闭这个链接
                closeConnectionAndLogExceptionsLocked(connection);
            } else {
                //判断这个数据库链接是否需要回收
                if (recycleConnectionLocked(connection, status)) {
                    //将这个链接添加到非主链接容器中
                    mAvailableNonPrimaryConnections.add(connection);
                }
                //判断队列其他waiter是否状态有更新
                wakeConnectionWaitersLocked();
            }
        }
    }
    
    //取消所有具有我们可以满足的要求的waiter的park,即唤醒该waiter对应的线程
    //这个方法并不会抛异常,而是将异常赋值给waiter进行抛出
    // Can't throw.
    @GuardedBy("mLock")
    private void wakeConnectionWaitersLocked() {
        // Unpark all waiters that have requests that we can fulfill.
        // This method is designed to not throw runtime exceptions, although we might send
        // a waiter an exception for it to rethrow.
        ConnectionWaiter predecessor = null;
        //链表的头结点
        ConnectionWaiter waiter = mConnectionWaiterQueue;
        boolean primaryConnectionNotAvailable = false;
        boolean nonPrimaryConnectionNotAvailable = false;
        while (waiter != null) {
            boolean unpark = false;
            //是否关闭了数据库,如果关闭了那么唤醒所有waiter的线程
            if (!mIsOpen) {
                unpark = true;
            } else {
                try {
                    SQLiteConnection connection = null;
                    //如果该waiter需要非主链接,而且现在有可用的非主链接
                    if (!waiter.mWantPrimaryConnection && !nonPrimaryConnectionNotAvailable) {
                        //获取非主链接
                        connection = tryAcquireNonPrimaryConnectionLocked(
                                waiter.mSql, waiter.mConnectionFlags); // might throw
                        //获取为空,标识现在没有可用的非主链接        
                        if (connection == null) {
                            nonPrimaryConnectionNotAvailable = true;
                        }
                    }
                    //主链接可以用
                    if (connection == null && !primaryConnectionNotAvailable) {
                        //尝试获取主链接
                        connection = tryAcquirePrimaryConnectionLocked(
                                waiter.mConnectionFlags); // might throw
                        //获取为空,标识现在主链接不可用
                        if (connection == null) {
                            primaryConnectionNotAvailable = true;
                        }
                    }
                    //获取到了数据库链接
                    if (connection != null) {
                        waiter.mAssignedConnection = connection;//改waiter赋值链接
                        unpark = true;//唤醒该waiter的对应线程
                    } else if (nonPrimaryConnectionNotAvailable && primaryConnectionNotAvailable) {
                        // There are no connections available and the pool is still open.
                        // We cannot fulfill any more connection requests, so stop here.
                        //连接池任然可用,但是没有可用的链接没法对其他的waiter状态做更新直接返回
                        break;
                    }
                } catch (RuntimeException ex) {
                    // Let the waiter handle the exception from acquiring a connection.
                    waiter.mException = ex;
                    unpark = true;
                }
            }
    
            final ConnectionWaiter successor = waiter.mNext;
            //如果需要唤醒,那么从链表中删除这个waiter,并进行对应线程唤醒操作
            if (unpark) {
                if (predecessor != null) {
                    predecessor.mNext = successor;
                } else {
                    mConnectionWaiterQueue = successor;
                }
                waiter.mNext = null;
                LockSupport.unpark(waiter.mThread);
            } else {
                predecessor = waiter;
            }
            waiter = successor;
        }
    }
    

    链接的释放有时候是为了回收,有时候为了重用。重用的时候还需要唤醒等待链接队列中获得这个链接的waiter

    数据库链接池的关闭

    说到数据库连接池的关闭,我们会联想到数据库的关闭数据库链接的关闭

    //SQLiteClosable.java,它是SQLiteDatabase的父类
    /**
     * Releases a reference to the object, closing the object if the last reference
     * was released.
     *
     * Calling this method is equivalent to calling {@link #releaseReference}.
     *
     * @see #releaseReference()
     * @see #onAllReferencesReleased()
     */
    //释放引用的对象,直到所有的引用都被释放了那么关闭数据库
    public void close() {
        releaseReference();
    }
    public void releaseReference() {
        boolean refCountIsZero = false;
        synchronized(this) {
            refCountIsZero = --mReferenceCount == 0;
        }
        if (refCountIsZero) {
            onAllReferencesReleased();
        }
    }
    //SQLiteDatabase.java
    @Override
    protected void onAllReferencesReleased() {
        dispose(false);
    }
    private void dispose(boolean finalized) {
        final SQLiteConnectionPool pool;
        synchronized (mLock) {
            if (mCloseGuardLocked != null) {
                if (finalized) {
                    mCloseGuardLocked.warnIfOpen();
                }
                mCloseGuardLocked.close();
            }
            //连接池置空,无法进行新操作
            pool = mConnectionPoolLocked;
            mConnectionPoolLocked = null;
        }
        if (!finalized) {
            //删除当前数据库的引用
            synchronized (sActiveDatabases) {
                sActiveDatabases.remove(this);
            }
            //关闭数据库连接池
            if (pool != null) {
                pool.close();
            }
        }
    }
    

    我们可以看到数据库连接池的关闭是由数据库关闭引起的。

    关闭数据库连接池

    //SQLiteConnectionPool.java
    /**
     * Closes the connection pool.
     * <p>
     * When the connection pool is closed, it will refuse all further requests
     * to acquire connections.  All connections that are currently available in
     * the pool are closed immediately.  Any connections that are still in use
     * will be closed as soon as they are returned to the pool.
     * </p>
     *
     * @throws IllegalStateException if the pool has been closed.
     */
    //关闭数据库连接池,停止接受新的数据库链接的请求。
    //链接池中的可用链接立即被关闭,其他正在使用的链接被归还到数据的时候关闭
    public void close() {
        dispose(false);
    }
    private void dispose(boolean finalized) {
        if (mCloseGuard != null) {
            if (finalized) {
                mCloseGuard.warnIfOpen();
            }
            mCloseGuard.close();
        }
        if (!finalized) {
            // Close all connections.  We don't need (or want) to do this
            // when finalized because we don't know what state the connections
            // themselves will be in.  The finalizer is really just here for CloseGuard.
            // The connections will take care of themselves when their own finalizers run.
            synchronized (mLock) {
                throwIfClosedLocked();//检测是否已经被关闭
                mIsOpen = false;//标识数据库连接池关闭
                //关闭数据库连接池中目前可用的链接(空闲的数据库链接、包括空闲的主链接)
                closeAvailableConnectionsAndLogExceptionsLocked();
                final int pendingCount = mAcquiredConnections.size();
                //任然有链接正在使用中
                if (pendingCount != 0) {
                    Log.i(TAG, "The connection pool for " + mConfiguration.label
                            + " has been closed but there are still "
                            + pendingCount + " connections in use.  They will be closed "
                            + "as they are released back to the pool.");
                }
                //判断队列其他waiter是否状态有更新
                wakeConnectionWaitersLocked();
            }
        }
    }
    

    关闭数据库链接

    //SQLiteConnectionPool.java
    // Can't throw.
    @GuardedBy("mLock")
    private void closeAvailableConnectionsAndLogExceptionsLocked() {
        //关闭可用的非主链接
        closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
        //如果主链接可用,那么关闭主链接
        if (mAvailablePrimaryConnection != null) {
            closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
            mAvailablePrimaryConnection = null;
        }
    }
    // Can't throw.
    @GuardedBy("mLock")
    private void closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked() {
        //循环遍历可用的非主链接,进行数据库链接的关闭
        final int count = mAvailableNonPrimaryConnections.size();
        for (int i = 0; i < count; i++) {
            closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i));
        }
        mAvailableNonPrimaryConnections.clear();
    }
    // Can't throw.
    @GuardedBy("mLock")
    private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection) {
        try {
            connection.close(); // might throw
            if (mIdleConnectionHandler != null) {
                mIdleConnectionHandler.connectionClosed(connection);
            }
        } catch (RuntimeException ex) {
            Log.e(TAG, "Failed to close connection, its fate is now in the hands "
                    + "of the merciful GC: " + connection, ex);
        }
    }
    
    • 数据库关闭的时候引用次数自减,若引用次数归零则真正执行关闭数据库;
    • 数据库关闭清楚引用后进行的是数据库连接池的关闭;
    • 数据库的关闭先状态,然后关闭所有的空闲链接,使用中的连接回归连接池后被关闭;

    文章到这里就全部讲述完啦,若有其他需要交流的可以留言哦

    想阅读作者的更多文章,可以查看我 个人博客 和公共号:
    振兴书城

    展开全文
  • python 连接数据库操作, 方法如下: 在本机的mysql 数据库中有一个名为yao的库,其中有一个名为user的表,表中的内容如图 下面,则是python连接数据库的方法,及查找出表中的内容,代码如下: #! /usr/bin/python #...
  • 新手都能看懂的代码! 完整的案例,md5加密,注解方式
  • 数据库连接池很简单,项目没使用框架亦可使用

    千次阅读 多人点赞 2020-04-08 10:38:25
    数据连接池可不是一项高端技术,JDBC早已实现对其的支持,还不快点来了解下。

    在前面我们讲解了JDBC的基本用法,包括Statement、PreparedStatement、CallableStatement接口的使用,存储过程、事务的使用方法。但是在我们每次操作数据库时,都会创建一个新的Connection,并且执行完毕后断开,Connection的新建、关闭操作十分的耗时并且耗资源,如果频繁的操作还会影响数据库服务器性能,过多的连接甚至会打崩数据库服务器。

    资源分配图

    ​SUN说:要有’光’,于是数据库连接池就诞生了。感谢之前的大神,让现在的开发者可以站在他们的肩膀上,在我们的项目中可以很方便的使用数据库连接池来解决上面说到的难题。下面就让我们一起来看下数据库连接池的概念和具体用法。

    1.数据库连接池的概念

    ​在JDBC编程中,每次创建和断开Connection对象都会消耗一定的时间和IO资源。这些问题在我们本地开发时可能还无法直观的体会到,但是如果项目的部署生产后,并发量达到一定程度时,问题就会暴露出来,而且是灾难性的。因此,数据库连接池就在千呼万唤中出现了。

    数据库连接池(DataBase Connection Pool)负责分配、管理和释放数据库连接,它允许应用程序重复使用现有的数据库连接,而不是重新建立。这个思想类似于目前比较流行的"共享"模式,当你有需要时,去租(申请)一个资源,在你使用完毕归还(释放)后,这些资源并不会被"销毁",而是被系统回收起来等待下一个用户申请。

    资源分配图

    ​上图为一个采用了数据库连接池操作数据库的简单示意图,从中我们可以看到当应用程序想操作数据库时,并不是向之前一样直接创建Connection,而是从数据库连接池中"申请"一个Connection;使用完毕后,连接池会将此Connection进行回收,并且可以将其交付给其他线程使用。并且连接池还可设置最大连接数,最小活跃数等配置,来减少数据库服务器的压力。

    2.JDBC之DataSource接口

    ​为了支持对数据库连接的管理,JDBC在3.0版本时提供了DataSource(数据源)接口,其作为首选方法替代DriverManager类负责建立与数据库的连接。DataSource接口位于javax.sql包中,接下来我们简单了解下。

    ​通过DataSource对象访问的驱动不会将自身注册到DriverManager中,DataSource的实现类通常会在JNDI(Java Naming and Directory Interface)注册。

    ​我们来看下DataSource接口中提供的获取Connection的方法:

    • Connection getConnection()
    • Connection getConnection(String username, String password)

    ​ 其中getConnection()方法的实现如下(以MySQL驱动为例),可以看到,无参的getConnection方法是使用DataSource中配置好的默认的用户。两个方法本质上是相同的,getConnection(String username, String password)方法可以指定创建连接使用的用户名和密码。

    //com.mysql.jdbc.jdbc2.optional.MysqlDataSource
    /**
     * Creates a new connection using the already configured username and
     * password.
     * 
     * @return a connection to the database
     * 
     * @throws SQLException
     *             if an error occurs
     */
    public java.sql.Connection getConnection() throws SQLException {
      return getConnection(this.user, this.password);
    }
    

    ​MySQL驱动中实现的DataSource,并没有实现数据库连接的池化管理,而是使用直连的方式通过Driver来获取的。而数据库连接池的实现主要一些开源组织来提供的,下面我们一起来看下目前主流的数据库连接池。

    3.数据库连接池简单对比

    ​现在使用比较多的数据库连接池DBCP、C3P0、BoneCP、Druid等,我们分别来简单的介绍下。

    1. C3p0: 支持JDBC2和JDBC3的标准规范,易于扩展。Hibernate、Spring框架支持。单线程,性能较差,适用于小型系统;
    2. DBCP:Apache组织下的开源连接池实现,也是Tomcat服务器使用的连接池组件, Jakarta commons-pool对象池机制。单线程,并发量低,性能不好,适用于小型系统;
    3. BoneCP:高效、免费、开源的Java数据库连接池实现库,速度最快,高度可扩展;
    4. Druid:Java语言中最好的数据库连接池,Druid还能够提供强大的监控和扩展功能,是一个可用于大数据实时查询和分析的高容错、高性能的开源分布式系统,尤其是当发生代码部署、机器故障以及其他产品系统遇到宕机等情况时,Druid仍能够保持100%正常运行。快速的交互式查询、高可用、可扩展。

    几个数据库连接池主要功能对比:

    DruidBoneCPDBCPC3P0
    LRU
    PSCache
    PSCache-Oracle-Optimized
    ExceptionSorter
    更新维护

    ​LRU(最近最久未使用)是一个性能关键指标,如果数据库连接池遵从LRU,有助于数据库服务器优化,这是重要的指标。在测试中,Druid、DBCP是遵守LRU的。BoneCP、C3P0则不是。BoneCP在mock环境下性能可能好,但在真实环境中就不好了。

    ​PSCache是数据库连接池的关键指标,即PrepareStatement的查询结果是否缓存。在Oracle中,类似SELECT NAME FROM USER WHERE ID = ?这样的SQL,启用PSCache和不启用PSCache的性能可能是相差一个数量级的。Proxool是不支持PSCache的数据库连接池,如果你使用Oracle、SQL Server、DB2、Sybase这样支持游标的数据库,那你就完全不用考虑Proxool。

    ​Oracle 10系列的Driver,如果开启PSCache,会占用大量的内存,必须做特别的处理,启用内部的EnterImplicitCache等方法优化才能够减少内存的占用。这个功能只有DruidDataSource有。如果你使用的是Oracle Jdbc,你应该毫不犹豫采用DruidDataSource。

    ​ExceptionSorter是一个很重要的容错特性,如果一个连接产生了一个不可恢复的错误,必须立刻从连接池中去掉,否则会连续产生大量错误。这个特性,目前只有Druid实现。Druid的实现参考自JBoss数据库连接池。

    ​经过对比发现,Druid当之无愧是Java语言中最好的数据库连接池。不仅提供了高效的数据库连接池,还提供了强大的监控与扩展功能。

    ​因此,我们下面使用Druid来作为我们的数据库连接池,并给出示例配置。

    4.Druid的使用

    ​Druid既然是Java语言里最好的数据库连接池,并且深受程序员圈的广泛认可,那我们自然也是一步上垒,直接动手撸代码。

    ​这里首先需要下载Druid的jar包(如果是maven项目,可以在pom文件中配置druid的依赖),我们选择1.1.21这个版本,下载地址:https://mvnrepository.com/artifact/com.alibaba/druid/1.1.21

    资源分配图

    ​我们将jar导入到lib目录中,并build path。然后,在src目录下新建druid.properties配置文件,目录结构如下图所示:

    资源分配图

    ​druid.properties中的属性如下,其中*是必须要配置的,其他属性可以选择性配置。

    #驱动类所在位置
    driverClassName=com.mysql.jdbc.Driver
    #数据库url*
    url=jdbc:mysql://localhost:3306/java_web?useSSL=false&characterEncoding=utf-8
    #用户名*
    username=root
    #密码*
    password=123456
    #最大活跃连接数*
    maxActive=20
    #初始化时建立物理连接的个数
    initialSize=1
    #获取连接最大等待时间,单位ms
    maxWait=60000
    #最小空闲连接数
    minIdle=5
    #空闲连接保持最大存活时间,单位ms
    minEvictableIdleTimeMillis=300000
    

    ​然后,我们在util包中新建DruidUtils,在其中创建数据源,并提供获取Connection的静态方法,其中的代码如下:

    import java.sql.Connection;
    import java.sql.SQLException;
    import java.util.Properties;
    
    import javax.sql.DataSource;
    
    import com.alibaba.druid.pool.DruidDataSourceFactory;
    
    public class DruidUtils {
    
    	private static DataSource dataSource;
    
    	private DruidUtils() {
    	}
    
    	// 因为静态方法已经初始化了DataSource,这里直接返回即可
    	public static DataSource getInstance() {
    		return dataSource;
    	}
    
    	// 创建数据源,根据配置文件中的属性初始化数据库连接池
    	static {
    		Properties pro = new Properties();
    		try {
    			pro.load(DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
    			dataSource = DruidDataSourceFactory.createDataSource(pro);
    		} catch (Exception e) {
    			// 异常处理
    			e.printStackTrace();
    		}
    	}
    
    	/**
    	 * 从数据库连接池中获取连接
    	 * 
    	 * @return
    	 * @throws SQLException
    	 */
    	public static Connection getConnection() throws SQLException {
    		return dataSource.getConnection();
    	}
    }
    
    

    ​配置好DruidUtil后,其使用和之前的JDBC操作数据库基本相同,除了获取连接的方式由从DriverManager变为从DataSource中获取。我们简单的写个测试代码:

    public void selectById(int id) throws SQLException {
      Connection connection = null;
      Statement statement = null;
      ResultSet resultSet = null;
      try {
        // 获取数据库连接
        connection = DruidUtils.getConnection();
        // 创建Statement对象
        statement = connection.createStatement();
        String sql = "SELECT * FROM users WHERE id = " + id;
        // 获取查询结果集
        resultSet = statement.executeQuery(sql);
        User user = new User();
        while (resultSet.next()) {
          user.setId(resultSet.getInt("id"));
          user.setUserName(resultSet.getString("name"));
          user.setPassword(resultSet.getString("password"));
          user.setEmail(resultSet.getString("email"));
          user.setBirthday(resultSet.getDate("birthday"));
          System.out.println(user);
        }
      } catch (SQLException e) {
        e.printStackTrace();
        throw e;
      } finally {
        //此处的释放资源代码可参考前几篇博客
        JDBCUtil.release(resultSet, statement, connection);
      }
    }
    

    ​我们执行测试代码selectById(2),运行结果如下图所示:

    资源分配图

    ​在上面的的代码中,我们在finally代码块中进行了资源释放,这里大家可能会有些疑惑,就是获取的连接如果关闭掉的话,那下次使用不还是要新建连接了么?为了解答疑惑,这里我们还看下druid中的源码,

    //druid中重写的getConnection方法,可以看到,其返回值的类型为DruidPooledConnection
    @Override
    public DruidPooledConnection getConnection() throws SQLException {
      return getConnection(maxWait);
    }
    
    资源分配图

    ​从上图中,我们可以看到DruidPooledConnection类是是继承了Connection接口的,而且还是支持数据库数据库连接池。并且在DruidPooledConnection类中,还重写了Connection接口中的close方法,源码如下:

    @Override
    public void close() throws SQLException {
      //如果已经释放,直接返回
      if (this.disable) {
        return;
      }
      //判断是否被重复close
      DruidConnectionHolder holder = this.holder;
      if (holder == null) {
        if (dupCloseLogEnable) {
          LOG.error("dup close");
        }
        return;
      }
      //回收此连接
      //...
    
      //设置回收成功标志位
      this.disable = true;
    }
    

    ​因此,我们在使用数据库连接池时,还可以保持着使用JDBC编程的习惯,但是应用程序处理数据库操作并发的能力却悄然的大幅提升。

    5.总结

    ​数据库连接池自其诞生以来,就广泛的应用于项目程序中,其对性能的提升是毋庸置疑的,并且,JDBC还新增了DataSource接口来支持数据库连接池,因此,我们需要掌握这把利器,并能使用好它。

    ​并且Spring、SpringBoot等框架对Druid的支持非常好,让我们可以快速的完成Druid配置,并且Druid还提供的监控的功能,可以满足你更多的需求。

    参考阅读:

    1. JNDI
    2. Druid常见问题
    3. Druid Git Hub

    ​又到了分隔线以下,本文到此就结束了,本文内容全部都是由博主自己进行整理并结合自身的理解进行总结,如果有什么错误,还请批评指正。

    ​Java web这一专栏会是一个系列博客,喜欢的话可以持续关注,如果本文对你有所帮助,还请还请点赞、评论加关注。

    ​有任何疑问,可以评论区留言。

    展开全文
  • 自定义实现的数据库连接池,并进行加锁,保证线程安全,适合初学者学习。
  • 数据库连接源代码

    2018-05-31 19:40:13
    支持ASP asp.net,链接ACCESS数据库,通过简单数据库链接数据库
  • 这是一款简单实用的php连接mysql数据库程序代码。 需要的朋友可以点击下面的连接下载源代码学习使用。
  • java 连接数据库,三步骤 加载驱动 获得连接 创建数据库操作对象 创建查询返回的结果集 当你需要什么的时候,在相应的方法获取即可
  • 一个简单的delphi连接数据库的demo,能够执行更新的操作。连接方式是采用window验证的连接方式
  • 我们在学习工作中可能常使用Hibernate、Mybatis、jpa等等框架,这些框架都对数据库连接池有很好的封装,可能忽略了数据库底层的实现,今天我们就一起来看看如何手写一个简易数据库连接池,在此之前我们先回忆一下...

    我们在学习工作中可能常使用Hibernate、Mybatis、jpa等等框架,这些框架都对数据库连接池有很好的封装,可能忽略了数据库底层的实现,今天我们就一起来看看如何手写一个简易的数据库连接池,在此之前我们先回忆一下java连接数据库的步骤:

    1. 注册加载jdbc数据库驱动
      第一,把驱动程序载入到内存里;第二。把当前载入的驱动程序自己主动去DriverManager那注冊,DriverManager是JDBC规范中唯一的Java类。
      代码体现:
        Class.forName(DriverName)
      //当中DriverName=Driver包名。 
      //Oracle的DriverName=“oracle.jdbc.driver.OracleDriver“。 
      //MySql的DriverName=“com.mysql.jdbc.Driver“; 

       
    2. 得到连接对象 Connection
       
      /*要连接数据库,须要向java.sql.DriverManager请求并获得Connection对象,该对象就代表一个数据库的连接。      
      使用DriverManager的getConnection(Stringurl , String username ,  String password )方法传入指定的欲连接的数据库的路径、数据库的username与password来获得。      
        比如://连接MySql数据库,username和password都是root 
      */     
        String url ="jdbc:mysql://localhost:3306/test";      
         String username = "root";   
         String password = "root" ; 
         try{      
           Connection con =DriverManager.getConnection(url ,username , password ) ;     
         }catch(SQLException se){      
           System.out.println("数据库连接失败!");   
            se.printStackTrace();    
         }
      
       
      
      /*1、DriverManager在JDBC规范中是类而不是接口。它是一个服务类,用于管理JDBC驱动程序,提供getConnection()方法建立应用程序与数据库的连接。当JDBC驱动程序载入到内存时,会自己主动向DriverManager注冊。此行代码发出连接请求,DriverManager类就会用注冊的JDBC驱动程序来创建到数据库的连接。
      
      2、DriverManager.getConnection()是个静态方法。
      
      3、DriverManager在java.sql包中。当我们调用sql包里不论什么一个类(包含接口)的不论什么一个方法时都会报一个编译时异常SQLException。这里我们使用一个try块后跟多个catch块解决。
      
      4、方法參数URL:统一资源定位符。我们连接的数据库在哪台主机上(这个通过ip地址确定),这个主机有可能装了好几种数据库软件,比方SqlServer,mysql,oracle,那么我们连接哪个数据库要通过port号来确定。port号又称服务号监听号,sqlserver为1433,mysql为3306,oracle为1521:。下表列出经常使用数据库软件的url写法:
      
      Oracle: jdbc:oracle:thin:@ip:1521:dbName;
      
      MySql:jdbc:mysql://ip:3306:dbName; 
      SQLServer:jdbc:sqlserver://ip:1443;databaseName=dbName;
      
      5、当使用本机ip地址连接时须要关闭防火墙。否则连接不上,使用localhost或127.0.0.1则不用关闭防火墙。
      */

       
    3. 创建 Statement对象
      /*1、运行静态SQL语句。通常通过Statement实例实现。   
      2、运行动态SQL语句。通常通过PreparedStatement实例实现。         
      3、运行数据库存储过程。通常通过CallableStatement实例实现。*/
      
            
      //详细的实现方式:不能防止sql注入        
      Statement stmt = con.createStatement();         
      
      
       //能防止sql注入
      PreparedStatement pstmt=con.prepareStatement(sql);         
      
      CallableStatement cstmt =con.prepareCall("{CALLdemoSp(? , ?
      
      )}") ;   

       
    4. 书写sql语句并执行sql
       

       /*Statement接口提供了三种运行SQL语句的方法:executeQuery、executeUpdate 和execute     
      
       1、ResultSet       executeQuery(String sql):运行查询数据库的SQL语句。返回一个结果集(ResultSet)对象。  
       2、int             executeUpdate(String sql):用于运行INSERT、UPDATE或DELETE语句以及SQL DDL语句,如:CREATETABLE和DROP TABLE等       
       3、execute(sql):用于运行返回多个结果集、多个更新计数或二者组合的 语句。*/
      
                   //详细实现的代码:
                  ResultSet rs = stmt.executeQuery("SELECT * FROM ...") ;  
                  int rows = stmt.executeUpdate("INSERTINTO ...") ;  
                  boolean flag =stmt.execute(String sql) ; 

       

    5. 处理结果 两种情况:
       

      /*  1、运行更新返回的是本次操作影响到的记录数。
      
          2、运行查询返回的结果是一个ResultSet对象。    
      
          ResultSet包括符合SQL语句中条件的全部行。而且它通过一套get方法提供了对这些行中数据的访问。*/
      
          //使用结果集(ResultSet)对象的访问方法获取数据:  
      
          while(rs.next()){      
      
          String name = rs.getString("name") ;  
      
          String pass = rs.getString(1) ; // 此方法比较高效     
      
       }      
         (列是从左到右编号的,而且从列1开始)

       

    6. 关闭资源释放资源

       

      /*操作完毕以后要把全部使用的JDBC对象全都关闭,以释放JDBC资源。关闭顺序和声明顺序相反:    
      
          1、关闭记录集   
      
          2、关闭声明   
      
          3、关闭连接对象
      */
      
                  if(rs != null){   // 关闭记录集      
      
         try{        
      
           rs.close() ; 
      
              }catch(SQLException e){   
      
                e.printStackTrace() ;  
      
             }           
      
       }       
      
          if(stmt != null){   // 关闭声明       
      
        try{          
      
         stmt.close() ;  
      
             }catch(SQLException e){ 
      
                  e.printStackTrace() ; 
      
              }        
      
         }          
      
       if(conn != null){  // 关闭连接对象   
      
             try{           
      
        conn.close() ;        
      
        }catch(SQLException e){       
      
            e.printStackTrace() ; 
      
               }          
      
        }



      客户端每访问一次数据库,数据库返回客户端数据,伴随的都是一次连接的创建和销毁。引入连接池主要的作用就是为了提高性能, 因为创建新的JDBC连接会导致联网操作和一定的JDBC驱动开销,其次才是减少资源开销。

      当某一线程需要用JDBC在MySQL或其他数据库上执行操作时,需要用到由连接池提供的连接。使用连接完成线程后,线程会将连接返回给连接池,以便该连接能够被其他需要使用连接的线程使用。从连接池“借出”连接时,该连接仅供请求它的线程使用。从编程观点看,其效果等同于每次需要JDBC连接时调用DriverManager.getConnection(),但是,采用连接池技术,可通过使用新的或已有的连接结束线程。连接池技术能显著增加Java应用程序的性能,同时还能降低资源使用率。

      市面上主流的连接池有c3p0、dbcp、druid、tomcat-jdbc、HikariCP(springboot2.0默认连接池)、proxool等...

      下面来手写实现简易的数据库连接池:
      连接池实际上就是一个容器,某一时刻可能有多个线程获取连接和释放连接,所以需要保证线程的安全性


       

      package cn.itcats.thread.datasoursepool;
      /**
       * 自定义数据库连接池
       * @author fatah
       */
      
      import java.sql.Connection;
      import java.sql.DriverManager;
      import java.sql.SQLException;
      import java.util.LinkedList;
      import java.util.concurrent.locks.Condition;
      import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReentrantLock;
      
      public class MyDataSourcePool {
      
      	// 使用容器——使用环境查询条件不多主要做增删改操作
      	private LinkedList<Connection> pool = new LinkedList<>();
      
      	private static final int INIT_CONNECTIONS = 10;
      	private static final String DRIVER_CLASS = "";
      	private static final String USERNAME = "";
      	private static final String PASSWORD = "";
      	private static final String URL = "";
      	private Lock lock = new ReentrantLock();
      	private Condition condition = lock.newCondition();
      
      	static {
      		// 注册驱动
      		try {
      			Class.forName("com.mysql.jdbc.Driver");
      		} catch (ClassNotFoundException e) {
      			e.printStackTrace();
      		}
      	}
      
      	// 通过构造方法初始化连接
      	public MyDataSourcePool() {
      		for (int i = 0; i < INIT_CONNECTIONS; i++) {
      			try {
      				Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
      				pool.addLast(conn);
      			} catch (SQLException e) {
      				e.printStackTrace();
      			}
      		}
      	}
      
      	// 获取数据库连接
      	public Connection getConnection() {
      		Connection conn = null;
      		lock.lock();
      		try {
      			while (pool.size() < 0) {
      				try {
      					condition.await();
      				} catch (InterruptedException e) {
      					e.printStackTrace();
      				}
      			}
      			if (!pool.isEmpty()) {
      				conn = pool.removeFirst();
      			}
      			return conn;
      		} finally {
      			lock.unlock();
      		}
      	}
      
      	// 释放数据库连接
      	public void releaseConnection(Connection conn) {
      		if (conn != null) {
      			lock.lock();
      			try {
      				// 释放连接过程就是把连接放回连接池过程
      				pool.addLast(conn);
      				condition.signal();
      			} finally {
      				lock.unlock();
      			}
      		}
      	}
      }
      

       

    展开全文
  • 本项目使用JDBC连接技术,实现登录后陈列数据库中全部人员信息的简单管理系统,在JDBC这里有问题的可以下载作为参考,附数据库
  • 主要介绍了php连接oracle数据库的方法,简单分析了php连接Oracle数据库的常见方法与具体操作技巧,并对可能出现的问题进行了总结分析,需要的朋友可以参考下
  • 这是一个简单的springmvc连接数据库的,在xml文件中配置数据库连接,适合初学springmvc,简单易懂

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 923,032
精华内容 369,212
关键字:

数据库连接简单吗