精华内容
下载资源
问答
  • 最近在开发过程中,小伙伴们有一个疑问:我们在写一个服务层的方法需要多次请求mybatis的dao(即mybatis的Mapper)方法,那会不会因为频繁跟数据库交互导致性能走低呢? 跟着疑问我们结合demo分析下mybatis的源码,来...

    最近在开发过程中,小伙伴们有一个疑问:我们在写一个服务层的方法需要多次请求mybatis的dao(即mybatis的Mapper)方法,那会不会因为频繁跟数据库交互导致性能走低呢?
    跟着疑问我们结合demo分析下mybatis的源码,来一层层解开大家的疑惑

    第一步:调用dao层的方法上不加事务@transactional
    在这里插入图片描述
    从日志可以看出,在没有加事务的情况下,确实是Mapper的每次请求数据库,都会创建一个SqlSession与数据库交互

    第二步:我们再看看加了事务的情况:
    在这里插入图片描述

    从日志可以看出,在方法中加了事务后,两次请求只创建了一个SqlSession

    看到以上两个日志对比图,我们可能会问为什么加了事务就公用一个Sqlsession,好,别急,下面我们会根据源码给大家一一解析

    Mapper的实现类是一个代理,真正执行逻辑的是MapperProxy.invoke(),该方法最终执行的是sqlSessionTemplate。

    org.mybatis.spring.SqlSessionTemplate:

    private final SqlSession sqlSessionProxy;

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, “Property ‘sqlSessionFactory’ is required”);
    notNull(executorType, “Property ‘executorType’ is required”);

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
    SqlSessionFactory.class.getClassLoader(),
    new Class[] { SqlSession.class },
    new SqlSessionInterceptor());
    }
    这个是创建SqlSessionTemplate的最终构造方法,可以看出sqlSessionTemplate中用到了SqlSession,是SqlSessionInterceptor实现的一个动态代理类,所以我们直接深入要塞:

    private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    SqlSession sqlSession = getSqlSession(
    SqlSessionTemplate.this.sqlSessionFactory,
    SqlSessionTemplate.this.executorType,
    SqlSessionTemplate.this.exceptionTranslator);
    try {
    Object result = method.invoke(sqlSession, args);
    if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
    // force commit even on non-dirty sessions because some databases require
    // a commit/rollback before calling close()
    sqlSession.commit(true);
    }
    return result;
    } catch (Throwable t) {
    Throwable unwrapped = unwrapThrowable(t);
    if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
    // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
    closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
    sqlSession = null;
    Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
    if (translated != null) {
    unwrapped = translated;
    }
    }
    throw unwrapped;
    } finally {
    if (sqlSession != null) {
    closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
    }
    }
    }
    }
    Mapper所有的方法,最终都会用这个方法来处理所有的数据库操作,其实spring整合mybatis和mybatis单独使用其实没区别,区别就是spring封装了所有处理细节,你就不用写大量的冗余代码,专注于业务开发。

    该动态代理方法主要做了以下处理:

    1、根据当前条件获取一个SqlSession,此时SqlSession可能是新创建的也有可能是获取到上一次请求的SqlSession;
    2、反射执行SqlSession方法,再判断当前会话是否是一个事务,如果是一个事务,则不commit;
    3、如果此时抛出异常,判断如果是PersistenceExceptionTranslator且不为空,那么就关闭当前会话,并且将sqlSession置为空防止finally重复关闭,PersistenceExceptionTranslator是spring定义的数据访问集成层的异常接口;
    4、finally无论怎么执行结果如何,只要当前会话不为空,那么就会执行关闭当前会话操作,关闭当前会话操作又会根据当前会话是否有事务来决定会话是释放还是直接关闭。

    org.mybatis.spring.SqlSessionUtils#getSqlSession:

    public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
    return session;
    }

    if (LOGGER.isDebugEnabled()) {
    LOGGER.debug(“Creating a new SqlSession”);
    }

    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
    }
    是不是看到了不服跑个demo时看到的日志“Creating a new SqlSession”了。在这个方法当中,首先是从TransactionSynchronizationManager(以下称当前线程事务管理器)获取当前线程threadLocal是否有SqlSessionHolder,如果有就从SqlSessionHolder取出当前SqlSession,如果当前线程threadLocal没有SqlSessionHolder,就从sessionFactory中创建一个SqlSession,具体的创建步骤上面已经说过了,接着注册会话到当前线程threadLocal中。

    先来看看当前线程事务管理器的结构:

    public abstract class TransactionSynchronizationManager {
    // …
    // 存储当前线程事务资源,比如Connection、session等
    private static final ThreadLocal<Map<Object, Object>> resources =
    new NamedThreadLocal<>(“Transactional resources”);
    // 存储当前线程事务同步回调器
    // 当有事务,该字段会被初始化,即激活当前线程事务管理器
    private static final ThreadLocal<Set> synchronizations =
    new NamedThreadLocal<>(“Transaction synchronizations”);
    // …
    }
    这是spring的一个当前线程事务管理器,它允许将当前资源存储到当前线程ThreadLocal中,从前面也可看出SqlSessionHolder是保存在resources中。
    org.mybatis.spring.SqlSessionUtils#registerSessionHolder:

    private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    SqlSessionHolder holder;
    // 判断当前是否有事务
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
    Environment environment = sessionFactory.getConfiguration().getEnvironment();
    // 判断当前环境配置的事务管理工厂是否是SpringManagedTransactionFactory(默认)
    if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
    if (LOGGER.isDebugEnabled()) {
    LOGGER.debug(“Registering transaction synchronization for SqlSession [” + session + “]”);
    }

      holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
      // 绑定当前SqlSessionHolder到线程ThreadLocal中
      TransactionSynchronizationManager.bindResource(sessionFactory, holder);
      // 注册SqlSession同步回调器
      TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
      holder.setSynchronizedWithTransaction(true);
      // 会话使用次数+1
      holder.requested();
    } else {
      if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
        }
      } else {
        throw new TransientDataAccessResourceException(
          "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
      }
    }
    

    } else {
    if (LOGGER.isDebugEnabled()) {
    LOGGER.debug(“SqlSession [” + session + “] was not registered for synchronization because synchronization is not active”);
    }
    }
    }

    注册SqlSession到当前线程事务管理器的条件首先是当前环境中有事务,否则不注册,判断是否有事务的条件是synchronizations的ThreadLocal是否为空:

    public static boolean isSynchronizationActive() {
    return (synchronizations.get() != null);
    }
    每当我们开启一个事务,会调用initSynchronization()方法进行初始化synchronizations,以激活当前线程事务管理器。

    public static void initSynchronization() throws IllegalStateException {
    if (isSynchronizationActive()) {
    throw new IllegalStateException(“Cannot activate transaction synchronization - already active”);
    }
    logger.trace(“Initializing transaction synchronization”);
    synchronizations.set(new LinkedHashSet());
    }
    所以当前有事务时,会注册SqlSession到当前线程ThreadLocal中。

    Mybatis自己也实现了一个自定义的事务同步回调器SqlSessionSynchronization,在注册SqlSession的同时,也会将SqlSessionSynchronization注册到当前线程事务管理器中,它的作用是根据事务的完成状态回调来处理线程资源,即当前如果有事务,那么当每次状态发生时就会回调事务同步器,具体细节可移步至Spring的org.springframework.transaction.support包。

    回到SqlSessionInterceptor代理类的逻辑,发现判断会话是否需要提交要调用以下方法:

    org.mybatis.spring.SqlSessionUtils#isSqlSessionTransactional:

    public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {
    notNull(session, NO_SQL_SESSION_SPECIFIED);
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    return (holder != null) && (holder.getSqlSession() == session);
    }
    取决于当前SqlSession是否为空并且判断当前SqlSession是否与ThreadLocal中的SqlSession相等,前面也分析了,如果当前没有事务,SqlSession是不会保存到事务同步管理器的,即没有事务,会话提交。

    org.mybatis.spring.SqlSessionUtils#closeSqlSession:

    public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
    notNull(session, NO_SQL_SESSION_SPECIFIED);
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    if ((holder != null) && (holder.getSqlSession() == session)) {
    if (LOGGER.isDebugEnabled()) {
    LOGGER.debug(“Releasing transactional SqlSession [” + session + “]”);
    }
    holder.released();
    } else {
    if (LOGGER.isDebugEnabled()) {
    LOGGER.debug(“Closing non transactional SqlSession [” + session + “]”);
    }
    session.close();
    }
    }
    方法无论执行结果如何都需要执行关闭会话逻辑,这里的判断也是判断当前是否有事务,如果SqlSession在事务当中,则减少引用次数,没有真实关闭会话。如果当前会话不存在事务,则直接关闭会话。

    最后总结
    涉及到了Spring的自定义事务的一些机制,其中当前线程事务管理器是整个事务的核心与中轴,当前有事务时,会初始化当前线程事务管理器的synchronizations,即激活了当前线程同步管理器,当Mybatis访问数据库会首先从当前线程事务管理器获取SqlSession,如果不存在就会创建一个会话,接着注册会话到当前线程事务管理器中,如果当前有事务,则会话不关闭也不commit,Mybatis还自定义了一个TransactionSynchronization,用于事务每次状态发生时回调处理。

    展开全文
  • 本例采用mysql数据库,因此请先下载mysql-connection.jar 众所周知,创建数据库连接需要消耗较多的资源,且创建时间也较长。...那么我们可以根据需要创建一个连接池,它负责分配、管理和释放数据库连接,它允许

    本例采用mysql数据库,因此请先下载mysql-connection.jar

    在我们的实际开发中,离不开和数据库打交道。而和数据库的通信,离不开数据库连接。
    通常用JDBC连接数据库时,需要加载数据驱动,然后再通过接口返回数据库连接。
    一般分为两步:
    1、加载驱动至内存
    Class.forName(“com.mysql.jdbc.Driver”);

    2、创建并获取连接,返回的是JDBC中的Connection
    DriverManager.getConnection(url, user, password)

    示例:

    //要连接的数据库URL
    String url = "jdbc:mysql://localhost:3306/tangwmdb";
    //连接的数据库时使用的用户名
    String username = "root";
    //连接的数据库时使用的密码
    String password = "root";
    
    //1.加载驱动
    //DriverManager.registerDriver(new com.mysql.jdbc.Driver());不推荐使用这种方式来加载驱动
    Class.forName("com.mysql.jdbc.Driver");//推荐使用这种方式来加载驱动
    
    //2.获取与数据库的链接
    Connection conn = DriverManager.getConnection(url, username, password);
    
    //3.获取用于向数据库发送sql语句的statement
    Statement st = conn.createStatement();
    
    String sql = "select id,name,password from members";
    //4.向数据库发sql,并获取代表结果集的resultset
    ResultSet rs = st.executeQuery(sql);
    
    //5.取出结果集的数据
    while(rs.next()){
        System.out.println("id=" + rs.getObject("id"));
        System.out.println("name=" + rs.getObject("name"));
        System.out.println("password=" + rs.getObject("password"));
    }
    
    //6.关闭链接,释放资源
    rs.close();
    st.close();
    conn.close();

    众所周知,创建数据库连接需要消耗较多的资源,且创建时间也较长。如果网站一天100万PV(假设每个页面都有DB读取或修改操作),程序就需要创建100万次连接,极大的浪费资源。
    事实上,同一时间需要创建数据库连接的请求数量并不多,一般几百个足够了。那么我们可以根据需要创建一个连接池,它负责分配、管理和释放数据库连接,它允许应用程序重复使用同一个现有的数据库连接,而不是重新建立一个。这里用到了设计模式中的一个模式:享元模式(Flyweight)
    比如我们的连接池中有1000条连接,请求来时,连接池从池中分配一条给请求,用完后收回,而不是销毁,等到下次有请求来时,又可以重复分配使用。
    这里写图片描述

    当使用了数据库连接池之后,在项目的实际开发中就不需要编写连接数据库的代码了,直接从数据源获得数据库的连接。比如:

    //DBCP 数据库连接池
    DataSource ds = BasicDataSourceFactory.createDataSource(prop);
    Connection conn = ds.getConnection();

    可以看到创建连接的工作很简单,因为复杂的分配、回收功能都交给了连接池去处理。

    当前有一些开源的数据连接池实现:

    • DBCP 数据库连接池
    • C3P0 数据库连接池

    另外阿里开源项目Druid(整个项目由数据库连接池、插件框架和SQL解析器组成)中的数据库连接池被很多互联网公司都采用在生产环境中。

    编写自己的数据库连接池

    编写的连接池需要做到以下几个基本点:
    1、可配置并管理多个连接节点的连接池
    这里写图片描述

    2、始使化时根据配置中的初始连接数创建指定数量的连接
    3、在连接池没有达到最大连接数之前,如果有可用的空闲连接就直接使用空闲连接,如果没有,就创建新的连接。
    4、当连接池中的活动连接数达到最大连接数,新的请求进入等待状态,直到有连接被释放。
    5、由于数据库连接闲置久了会超时关闭,因此需要连接池采用机制保证每次请求的连接都是有效可用的。
    6、线程安全
    7、连接池内部要保证指定最小连接数量的空闲连接。
    对于最小连接数在实际应用中的效果以及与初始连接数的区别,其实理解的不是很透。在程序中我采用的方式是,如果 活动连接数 + 空闲连接数 < 最小连接数,就补齐对应数量(最小连接数 - 活动连接数 - 空闲连接数)的空闲连接

    摘录一段:

    数据库连接池的最小连接数和最大连接数的设置要考虑到以下几个因素:
    最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费。
    最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作。
    如果最小连接数与最大连接数相差很大,那么最先连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接。不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,它将被放到连接池中等待重复使用或是超时后被释放。

    系统结构:

    1.连接池接口IConnectionPool:里面定义一些基本的获取连接的一些方法。
    2.连接池接口实现ConnectionPool
    3.连接池管理DBConnectionManager:管理不同的连接池,所有的连接都是通过这里获得。
    4.其它工具类,诸如属性读取类PropertiesManager,属性保存类DBPropertyBean。
    这里写图片描述

    工程结构:

    这里写图片描述

    工程代码:

    DBPropertyBean.java

    package com.twm.TDBConnectionPool;
    public class DBPropertyBean {
    
        private String nodeName;
        //数据连接驱动
        private String driverName;
        //数据连接url
        private String url;
        //数据连接username
        private String username;
        //数据连接密码
        private String password;
        //连接池最大连接数
        private int maxConnections ;
        //连接池最小连接数
        private int minConnections;
        //连接池初始连接数
        private int initConnections;
        //重连间隔时间 ,单位毫秒
        private int conninterval ;
        //获取连接超时时间 ,单位毫秒,0永不超时
        private int timeout ;
    
        //构造方法
        public DBPropertyBean(){
            super();
        }
    
        //下面是getter and setter
    
        /**
         * 获取数据库连接节点名称
         * @return
         */
        public String getNodeName() {
            return nodeName;
        }
    
        /**
         * 设置数据库连接节点名称
         * @param nodeName
         */
        public void setNodeName(String nodeName) {
            this.nodeName = nodeName;
        }
    
        /**
         * 获取数据库驱动
         * @return
         */
        public String getDriverName() {
            return driverName;
        }
    
        /**
         * 设置数据库驱动
         * @param driverName
         */
        public void setDriverName(String driverName) {
            this.driverName = driverName;
        }
    
        /**
         * 获取数据库url
         * @return
         */
        public String getUrl() {
            return url;
        }
    
        /**
         * 设置数据库url
         * @param url
         */
        public void setUrl(String url) {
            this.url = url;
        }
    
        /**
         * 获取用户名
         * @return
         */
        public String getUsername() {
            return username;
        }
    
        /**
         * 设置用户名
         * @param username
         */
        public void setUsername(String username) {
            this.username = username;
        }
    
        /**
         * 获取数据库连接密码
         * @return
         */
        public String getPassword(){
            return password;
        }
    
        /**
         * 设置数据库连接密码
         * @param password
         */
        public void setPassword(String password) {
            this.password = password;
        }
    
        /**
         * 获取最大连接数
         * @return
         */
        public int getMaxConnections() {
            return maxConnections;
        }
    
        /**
         * 设置最大连接数
         * @param maxConnections
         */
        public void setMaxConnections(int maxConnections) {
            this.maxConnections = maxConnections;
        }
    
        /**
         * 获取最小连接数(也是数据池初始连接数)
         * @return
         */
        public int getMinConnections() {
            return minConnections;
        }
    
        /**
         * 设置最小连接数(也是数据池初始连接数)
         * @param minConnections
         */
        public void setMinConnections(int minConnections) {
            this.minConnections = minConnections;
        }
    
        /**
         * 获取初始加接数
         * @return
         */
        public int getInitConnections() {
            return initConnections;
        }
    
        /**
         * 设置初始连接数
         * @param initConnections
         */
        public void setInitConnections(int initConnections) {
            this.initConnections = initConnections;
        }
    
        /**
         * 获取重连间隔时间,单位毫秒
         * @return
         */
        public int getConninterval() {
            return conninterval;
        }
    
        /**
         * 设置重连间隔时间,单位毫秒
         * @param conninterval
         */
        public void setConninterval(int conninterval) {
            this.conninterval = conninterval;
        }
    
        /**
         * 获取连接超时时间,单位毫秒
         * @return
         */
        public int getTimeout() {
            return timeout;
        }
    
        /**
         * 设置连接超时时间 ,单位毫秒,0-无限重连
         * @param timeout
         */
        public void setTimeout(int timeout) {
            this.timeout = timeout;
        }
    
    }

    IConnectionPool.java

    package com.twm.TDBConnectionPool;
    
    import java.sql.Connection;
    import java.sql.SQLException;
    
    public interface IConnectionPool {
        /**
         * 获取一个数据库连接,如果等待超过超时时间,将返回null
         * @return 数据库连接对象
         */
        public Connection getConnection();
    
        /**
         * 获得当前线程的连接库连接
         * @return 数据库连接对象
         */
        public Connection getCurrentConnecton();
    
        /**
         * 释放当前线程数据库连接
         * @param conn 数据库连接对象
         * @throws SQLException
         */
        public void releaseConn(Connection conn) throws SQLException;
    
        /**
         * 销毁清空当前连接池
         */
        public void destroy();
    
        /**
         * 连接池可用状态
         * @return 连接池是否可用
         */
        public boolean isActive();
    
        /**
         * 定时器,检查连接池
         */
        public void checkPool();
    
        /**
         * 获取线程池活动连接数
         * @return 线程池活动连接数
         */
        public int getActiveNum();
    
        /**
         * 获取线程池空闲连接数
         * @return 线程池空闲连接数
         */
        public int getFreeNum();
    }

    ConnectionPool.java

    package com.twm.TDBConnectionPool;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.sql.Connection;
    import java.sql.Driver;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.TimerTask;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    import org.apache.log4j.Logger;
    
    
    /** 
     * 类说明 :友元类,包内可见,不提供给客户程序直接访问。
     */
    class ConnectionPool implements IConnectionPool {
    
        private static final Logger log = Logger.getLogger(ConnectionPool.class);
    
        private DBPropertyBean propertyBean=null;
    
        //连接池可用状态
        private Boolean isActive = true;
    
        // 空闲连接池 。由于List读写频繁,使用LinkedList存储比较合适
        private LinkedList<Connection> freeConnections = new LinkedList<Connection>();  
        // 活动连接池。活动连接数 <= 允许最大连接数(maxConnections)
        private LinkedList<Connection> activeConnections = new LinkedList<Connection>(); 
    
        //当前线程获得的连接
        private ThreadLocal<Connection> currentConnection= new ThreadLocal<Connection>();
    
        //构造方法无法返回null,所以取消掉。在下面增加了CreateConnectionPool静态方法。
        private ConnectionPool(){
            super();
        }
    
        public static ConnectionPool CreateConnectionPool(DBPropertyBean propertyBean) {
            ConnectionPool connpool=new ConnectionPool();
            connpool.propertyBean = propertyBean;
    
            //加载驱动 
    
            //在多节点环境配置下,因为在这里无法判断驱动是否已经加载,可能会造成多次重复加载相同驱动。
            //因此加载驱动的动作,挪到connectionManager管理类中去实现了。
            /*try {
                Class.forName(connpool.propertyBean.getDriverName());
                log.info("加载JDBC驱动"+connpool.propertyBean.getDriverName()+"成功");
            } catch (ClassNotFoundException e) {
                log.info("未找到JDBC驱动" + connpool.propertyBean.getDriverName() + ",请引入相关包");
                return null;
            }*/
    
            //基本点2、始使化时根据配置中的初始连接数创建指定数量的连接
            for (int i = 0; i < connpool.propertyBean.getInitConnections(); i++) {
                try {
                    Connection conn = connpool.NewConnection();
                    connpool.freeConnections.add(conn);
                } catch (SQLException | ClassNotFoundException e) {
                    log.error(connpool.propertyBean.getNodeName()+"节点连接池初始化失败");
                    return null;
                }
            }
    
            connpool.isActive = true;
            return connpool;
        }
    
    
    
        /**
         * 检测连接是否有效
         * @param 数据库连接对象
         * @return Boolean
         */
        private Boolean isValidConnection(Connection conn) throws SQLException{
            try {
                if(conn==null || conn.isClosed()){
                    return false;
                }
            } catch (SQLException e) {
                throw new SQLException(e);
            }
            return true;
        }
    
        /**
         * 创建一个新的连接
         * @return 数据库连接对象
         * @throws ClassNotFoundException
         * @throws SQLException
         */
        private Connection NewConnection() throws ClassNotFoundException,
                SQLException {
    
            Connection conn = null;
            try {
                if (this.propertyBean != null) {
                    //Class.forName(this.propertyBean.getDriverName());
                    conn = DriverManager.getConnection(this.propertyBean.getUrl(),
                            this.propertyBean.getUsername(),
                            this.propertyBean.getPassword());
                }
            } catch (SQLException e) {
                throw new SQLException(e);
            }
    
    
    
            return conn;
        }
    
    
        @Override
        public synchronized Connection getConnection() {
            Connection conn = null;
            if (this.getActiveNum() < this.propertyBean.getMaxConnections()) {
                // 分支1:当前使用的连接没有达到最大连接数  
                // 基本点3、在连接池没有达到最大连接数之前,如果有可用的空闲连接就直接使用空闲连接,如果没有,就创建新的连接。
                if (this.getFreeNum() > 0) {
                    // 分支1.1:如果空闲池中有连接,就从空闲池中直接获取
                    log.info("分支1.1:如果空闲池中有连接,就从空闲池中直接获取");
                    conn = this.freeConnections.pollFirst();
    
                    //连接闲置久了也会超时,因此空闲池中的有效连接会越来越少,需要另一个进程进行扫描监测,不断保持一定数量的可用连接。
                    //在下面定义了checkFreepools的TimerTask类,在checkPool()方法中进行调用。
    
                    //基本点5、由于数据库连接闲置久了会超时关闭,因此需要连接池采用机制保证每次请求的连接都是有效可用的。
                    try {
                        if(this.isValidConnection(conn)){
                            this.activeConnections.add(conn);
                            currentConnection.set(conn);
                        }else{
                            conn = getConnection();//同步方法是可重入锁
                        }
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                } else {
                    // 分支1.2:如果空闲池中无可用连接,就创建新的连接
                    log.info("分支1.2:如果空闲池中无可用连接,就创建新的连接");
                    try {
                        conn = this.NewConnection();
                        this.activeConnections.add(conn);
                    } catch (ClassNotFoundException | SQLException e) {
                        e.printStackTrace();
                    }
                }
            } else {
                // 分支2:当前已到达最大连接数  
                // 基本点4、当连接池中的活动连接数达到最大连接数,新的请求进入等待状态,直到有连接被释放。
                log.info("分支2:当前已到达最大连接数 ");
                long startTime = System.currentTimeMillis();
    
                //进入等待状态。等待被notify(),notifyALL()唤醒或者超时自动苏醒  
                try{
                    this.wait(this.propertyBean.getConninterval());  
                }catch(InterruptedException e) {  
                    log.error("线程等待被打断");  
                }
    
                //若线程超时前被唤醒并成功获取连接,就不会走到return null。
                //若线程超时前没有获取连接,则返回null。
                //如果timeout设置为0,就无限重连。
                if(this.propertyBean.getTimeout()!=0){
                    if(System.currentTimeMillis() - startTime > this.propertyBean.getTimeout())  
                        return null;  
                }
                conn = this.getConnection();
    
            }
            return conn;
        }
    
    
        @Override
        public Connection getCurrentConnecton() {
            Connection conn=currentConnection.get();
            try {
                if(! isValidConnection(conn)){
                    conn=this.getConnection();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return conn;
        }
    
    
        @Override
        public synchronized void releaseConn(Connection conn) throws SQLException {
    
            log.info(Thread.currentThread().getName()+"关闭连接:activeConnections.remove:"+conn);
            this.activeConnections.remove(conn);
            this.currentConnection.remove();
            //活动连接池删除的连接,相应的加到空闲连接池中
            try {
                if(isValidConnection(conn)){
                    freeConnections.add(conn);
                }else{
                    freeConnections.add(this.NewConnection());
                }
    
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
            }
            //唤醒getConnection()中等待的线程
            this.notifyAll();
        }
    
        @Override
        public synchronized void destroy() {
            for (Connection conn : this.freeConnections) {  
                try {
                    if (this.isValidConnection(conn)) { 
                        conn.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }   
            }  
            for (Connection conn : this.activeConnections) {  
                try {
                    if (this.isValidConnection(conn)) { 
                        conn.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                } 
            }
            this.isActive = false;
            this.freeConnections.clear();
            this.activeConnections.clear();
        }
    
        @Override
        public boolean isActive() {
            return this.isActive;
        }
    
    
        @Override
        public void checkPool() {
    
            final String nodename=this.propertyBean.getNodeName();
    
            ScheduledExecutorService ses=Executors.newScheduledThreadPool(2);
    
            //功能一:开启一个定时器线程输出状态
            ses.scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    System.out.println(nodename +"空闲连接数:"+getFreeNum());  
                    System.out.println(nodename +"活动连接数:"+getActiveNum());   
    
                }
            }, 1, 1, TimeUnit.SECONDS);
    
            //功能二:开启一个定时器线程,监测并维持空闲池中的最小连接数
            ses.scheduleAtFixedRate(new checkFreepools(this), 1, 5, TimeUnit.SECONDS);
        }
    
        @Override
        public synchronized int getActiveNum() {
            return this.activeConnections.size();
        }
    
        @Override
        public synchronized int getFreeNum() {
            return this.freeConnections.size();
        }
    
        //基本点6、连接池内部要保证指定最小连接数量的空闲连接
        class checkFreepools extends TimerTask {
            private ConnectionPool conpool = null;
    
            public checkFreepools(ConnectionPool cp) {
                this.conpool = cp;
            }
    
            @Override
            public void run() {
                if (this.conpool != null && this.conpool.isActive()) {
                    int poolstotalnum = conpool.getFreeNum()
                            + conpool.getActiveNum();
                    int subnum = conpool.propertyBean.getMinConnections()
                            - poolstotalnum;
    
                    if (subnum > 0) {
                        System.out.println(conpool.propertyBean.getNodeName()
                                + "扫描并维持空闲池中的最小连接数,需补充" + subnum + "个连接");
                        for (int i = 0; i < subnum; i++) {
                            try {
                                conpool.freeConnections
                                        .add(conpool.NewConnection());
                            } catch (ClassNotFoundException | SQLException e) {
                                e.printStackTrace();
                            }
                        }
    
                    }
                }
    
            }
    
        }
    
    
    
    }

    ConnectionManager.java

    package com.twm.TDBConnectionPool;
    
    import java.sql.Connection;
    import java.sql.Driver;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.HashSet;
    import java.util.Hashtable;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Queue;
    import java.util.Set;
    import java.util.concurrent.ConcurrentHashMap;
    
    import org.apache.log4j.Logger;
    import com.twm.TDBConnectionPool.util.PropertiesManager;
    
    
    public class ConnectionManager {
        private final Logger log = Logger.getLogger(ConnectionPool.class);
    
        private static ConnectionManager dbm = null;
    
        /** 
         * 加载的驱动器名称集合 
         */  
        private Set<String> drivers = new HashSet<String>(); 
    
        /**
         * 数据库连接池字典
         * 为每个节点创建一个连接池(可配置多个节点)
         */
        private ConcurrentHashMap<String, IConnectionPool> pools = new ConcurrentHashMap<String, IConnectionPool>();
    
    
    
        private ConnectionManager() {
            createPools();
        }
    
    
        /**
         * 装载JDBC驱动程序,并创建连接池
         */
        private void createPools() {
            String str_nodenames = PropertiesManager.getProperty("nodename");
            //基本点1、可配置并管理多个连接节点的连接池
            for (String str_nodename : str_nodenames.split(",")) {
                DBPropertyBean dbProperty = new DBPropertyBean();
                dbProperty.setNodeName(str_nodename);
    
                //验证url配置正确性
                String url = PropertiesManager.getProperty(str_nodename + ".url");
                if (url == null) {
                    log.error(str_nodename+"节点的连接字符串为空,请检查配置文件");
                    continue;
                }
                dbProperty.setUrl(url);
    
                //验证driver配置正确性
                String driver = PropertiesManager.getProperty(str_nodename + ".driver");
                if (driver == null) {
                    log.error(str_nodename+"节点的driver驱动为空,请检查配置文件");
                    continue;
                }
                dbProperty.setDriverName(driver);
    
    
                //验证user配置正确性
                String user = PropertiesManager.getProperty(str_nodename + ".user");
                if (user == null) {
                    log.error(str_nodename+"节点的用户名设置为空,请检查配置文件");
                    continue;
                }
                dbProperty.setUsername(user);
    
    
                //验证password配置正确性
                String password = PropertiesManager.getProperty(str_nodename + ".password");
                if (password == null) {
                    log.error(str_nodename+"节点的密码设置为空,请检查配置文件");
                    continue;
                }
                dbProperty.setPassword(password);
    
                //验证最小连接数配置正确性
                String str_minconnections=PropertiesManager.getProperty(str_nodename + ".minconnections");
                int minConn;
                try {
                    minConn = Integer.parseInt(str_minconnections);
                } catch (NumberFormatException e) {
                    log.error(str_nodename + "节点最小连接数设置错误,默认设为5");
                    minConn=5;
                }
                dbProperty.setMinConnections(minConn);
    
                //验证初始连接数配置正确性
                String str_initconnections=PropertiesManager.getProperty(str_nodename + ".initconnections");
                int initConn;
                try {
                    initConn = Integer.parseInt(str_initconnections);
                } catch (NumberFormatException e) {
                    log.error(str_nodename + "节点初始连接数设置错误,默认设为5");
                    initConn=5;
                }
                dbProperty.setInitConnections(initConn);
    
                //验证最大连接数配置正确性
                String str_maxconnections=PropertiesManager.getProperty(str_nodename + ".maxconnections");
                int maxConn;
                try {
                    maxConn = Integer.parseInt(str_maxconnections);
                } catch (NumberFormatException e) {
                    log.error(str_nodename + "节点最大连接数设置错误,默认设为20");
                    maxConn=20;
                }
                dbProperty.setMaxConnections(maxConn);
    
                //验证conninterval配置正确性
                String str_conninterval=PropertiesManager.getProperty(str_nodename + ".conninterval");
                int conninterval;
                try {
                    conninterval = Integer.parseInt(str_conninterval);
                } catch (NumberFormatException e) {
                    log.error(str_nodename + "节点重新连接间隔时间设置错误,默认设为500ms");
                    conninterval = 500;
                }
                dbProperty.setConninterval(conninterval);
    
                //验证timeout配置正确性
                String str_timeout=PropertiesManager.getProperty(str_nodename + ".timeout");
                int timeout;
                try {
                    timeout = Integer.parseInt(str_timeout);
                } catch (NumberFormatException e) {
                    log.error(str_nodename + "节点连接超时时间设置错误,默认设为2000ms");
                    timeout = 2000;
                }
                dbProperty.setTimeout(timeout);
    
                //创建驱动
                if(!drivers.contains(dbProperty.getDriverName())){
                    try {
                        Class.forName(dbProperty.getDriverName());
                        log.info("加载JDBC驱动"+dbProperty.getDriverName()+"成功");
                        drivers.add(dbProperty.getDriverName());
                    } catch (ClassNotFoundException e) {
                        log.error("未找到JDBC驱动" + dbProperty.getDriverName() + ",请引入相关包");
                        e.printStackTrace();
                    }
                }
    
                //创建连接池。这里采用同步方法实现的连接池类ConnectionPool。
                //(如果后面我们还有别的实现方式,只需要更改这里就行了。)
                IConnectionPool cp = ConnectionPool.CreateConnectionPool(dbProperty);
                if (cp != null) {
                    pools.put(str_nodename, cp);
                    cp.checkPool();
                    log.info("创建" + str_nodename + "数据库连接池成功");
                } else {
                    log.info("创建" + str_nodename + "数据库连接池失败");
                }
            }
    
        }
    
        /**
         * 获得单例
         * 
         * @return DBConnectionManager单例
         */
        public synchronized static ConnectionManager getInstance() {
            if (dbm == null) {
                dbm = new ConnectionManager();
            }
            return dbm;
        }
    
        /**
         * 从指定连接池中获取可用连接
         * 
         * @param poolName要获取连接的连接池名称
         * @return连接池中的一个可用连接或null
         */
        public Connection getConnection(String poolName) {
            IConnectionPool pool =  pools.get(poolName);
            return pool.getConnection();
        }
    
    
        /**
         * 回收指定连接池的连接
         * 
         * @param poolName连接池名称
         * @param conn要回收的连接
         */
        public void closeConnection(String poolName, Connection conn) throws SQLException {
            IConnectionPool pool = pools.get(poolName);
            if (pool != null) {
                try {
                    pool.releaseConn(conn);
                } catch (SQLException e) {
                    log.error("回收"+poolName+"池中的连接失败。");
                    throw new SQLException(e);
                }
            }else{
                log.error("找不到"+poolName+"连接池,无法回收");
            }
        }
    
        /**
         * 关闭所有连接,撤销驱动器的注册
         */
        public void destroy() {
            for (Map.Entry<String, IConnectionPool> poolEntry : pools.entrySet()) {
                IConnectionPool pool = poolEntry.getValue();
                pool.destroy();
            }
            log.info("已经关闭所有连接");
        }
    }

    PropertiesManager.java

    package com.twm.TDBConnectionPool.util;
    
    import java.io.IOException;
    import java.util.Enumeration;
    import java.util.Properties;
    
    
    public class PropertiesManager {
        private static Properties pro = new Properties();
    
        private PropertiesManager() {
    
        }
    
        static {
            try {
                pro.load(PropertiesManager.class.getClassLoader().getResourceAsStream("DB.properties"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static String getProperty(String key) {
            return pro.getProperty(key);
        }
    
        public static String getProperty(String key, String defaultValue) {
            return pro.getProperty(key, defaultValue);
        }
    
        public static Enumeration<?> propertiesNames() {
            return pro.propertyNames();
        }
    }

    testpool.java

    package com.twm.TDBConnectionPool.run;
    
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.Iterator;
    import java.util.List;
    
    import com.twm.TDBConnectionPool.ConnectionManager;
    import com.twm.TDBConnectionPool.DBPropertyBean;
    import com.twm.TDBConnectionPool.IConnectionPool;
    import com.twm.TDBConnectionPool.util.PropertiesManager;
    
    
    public class testpool {
    
        /**
         * @param args
         * @throws InterruptedException 
         */
        public static void main(String[] args) throws InterruptedException {
    
            List<Thread> threadlist=new ArrayList<Thread>();
            for(int i=1;i<=3;i++){
                Thread subThread = new Thread(new workrun(i));
                subThread.start();
                threadlist.add(subThread);
            }
            for (Iterator<Thread> iterator = threadlist.iterator(); iterator.hasNext();) {
                Thread thread = iterator.next();
                thread.join();
            }
            //ConnectionManager.getInstance().destroy();
    
        }
    
    }
    class workrun implements Runnable{
        int i;
        public workrun(int i){
            this.i=i;
        }
        @Override
        public void run() {
            ConnectionManager cm = ConnectionManager.getInstance();
    
             //1.从数据池中获取数据库连接
            Connection conn = cm.getConnection("default");
            System.out.println("线程 " + Thread.currentThread().getName() + "获得连接:" + conn);
    
            //模拟查询耗时操作
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
    
             //2.获取用于向数据库发送sql语句的statement
            Statement st = null;
            try {
                st = conn.createStatement();
            } catch (SQLException e) {
                e.printStackTrace();
            }
    
            String sql = "select * from product where id="+i;
            //3.向数据库发sql,并获取代表结果集的resultset
            //4.取出结果集的数据
            ResultSet rs = null;
            try {
                rs = st.executeQuery(sql);
                while(rs.next()){
                    System.out.println("productname=" + rs.getObject("productname"));
                    //System.out.println("price=" + rs.getObject("price"));
                    //System.out.println("cateid=" + rs.getObject("cateid"));
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
    
            //模拟查询耗时操作
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            //5.关闭链接,释放资源
            try {
                rs.close();
                st.close();
                //conn.close();
                cm.closeConnection("default",conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }
    
        }
    
    }

    DB.properties

    nodename=default,testdb
    
    default.driver=com.mysql.jdbc.Driver
    default.url=jdbc:mysql://localhost:3306/tangwenmingdb
    default.user=root
    default.password=root
    default.maxconnections=10
    default.minconnections=5
    default.initconnections=2
    default.conninterval=500
    default.timeout = 5000
    
    testdb.driver=com.mysql.jdbc.Driver
    testdb.url=jdbc:mysql://192.168.1.17:3306/test
    testdb.user=root
    testdb.password=root
    testdb.maxconnections=10
    testdb.minconnections=2
    testdb.initconnections=2
    testdb.conninterval= 500
    testdb.timeout = 5000

    product表结构和数据:

    /*!40101 SET NAMES utf8 */;
    create table `product` (
        `id` int (10),
        `productname` varchar (150),
        `price` Decimal (12),
        `cateid` int (11)
    ); 
    insert into `product` (`id`, `productname`, `price`, `cateid`) values('1','iphone 8 plus','5700.00','1');
    insert into `product` (`id`, `productname`, `price`, `cateid`) values('2','华为G7','1700.00','1');
    insert into `product` (`id`, `productname`, `price`, `cateid`) values('3','HTC','1200.00','1');

    这里写图片描述

    至此,我们用同步方法synchronized实现了一个简单的数据库连接池。大家可以试着调整DB.properties里的参数配置(比如maxconnections,minconnections,initconnections),观察控制台的输出变化。也可以调整程序中的thread.sleep时长观察。


    代理Connection

    现在来做一个优化,各位一定要看完,这里搞得我很痛苦,也留下一个未解决的问题。希望大家帮忙。
    通常,用户在使用完毕数据库连接后会调用conn.close()关闭连接,而不是调用我们程序中的ConnectionManager.closeConnection("default",conn);方法关闭。
    为了让conn.close和我们自己写的closeConnection()方法保持一致的效果,就必须要改写connection的close()方法,在这个场景下,很容易想到使用代理方式。这里先采用动态代理方式来实现:

    通过改造ConnectionPool类:
    1、在ConnectionPool类中添加一个内部InvocationHandler实现类(和checkFreepools类平级的)

    class ConProxyHandler implements InvocationHandler{
            private Connection conn;
            private ConnectionPool cp;
    
            public ConProxyHandler(ConnectionPool cp, Connection conn) {
                this.cp = cp;
                this.conn = conn;
            }
    
            @Override
    
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                if(method.getName().equals("close")){
                    cp.releaseConn(this.conn);
                    return null;
                }else{
                    //如果不是调用close方法,就原样处理
                    return method.invoke(this.conn, args);
                }
            }
    
        }

    2、改造ConnectionPool类的NewConnection()方法,在return conn之前,加上一段

    if (conn != null) {
        ConProxyHandler handler = new ConProxyHandler(this, conn);
        Connection conn_return = (Connection) Proxy.newProxyInstance(
                ConnectionPool.class.getClassLoader(),
                new Class[] { Connection.class }, handler);
        System.out.println("生成代理类:" + conn_return);
        return conn_return;
    }

    最后在调用的testpool.java中把

    cm.closeConnection("default",conn);

    改为:

    conn.close();

    然后运行,前面都正常,到最后出问题了:
    当三个线程分别关闭连接后,空闲连接数加上去了,但活动连接数并没有相应的减少。
    心碎。。。。到底哪出问题了呢?

    打断点跟踪发现this.activeConnections.remove(conn);这句并没有成功移除conn。
    然后开始检查:
    1、打印ConProxyHandler类中的cp和ConnectionPool类中CreateConnectionPool()方法里的connpool,发现hashcode是一致的
    2、在ConProxyHandler类中的invoke方法中加上对equals方法的处理。发现压根不会进到这里。
    3、跟踪到this.activeConnections.remove(conn);
    发现this.activeConnections里保存了三个连接类
    这里写图片描述

    并且传入参数conn的hashcode和this.activeConnections中其中一个连接类是一致的

    这里写图片描述

    但是运行完this.activeConnections.remove(conn)后到下句,发现this.activeConnections里依然存在com.mysql.jdbc.JDBC4Connection@109cdd5对象。

    为什么删不掉呢?
    太打击积极性了,也不知道怎么调试查看原因。开始怀疑自己还适合做开发吗?

    即然删不掉,那就将this.activeConnections改成hashmap,用key来删掉。
    改动ConnectionPool .java如下:

    package com.twm.TDBConnectionPool;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.sql.Connection;
    import java.sql.Driver;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.Hashtable;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.TimerTask;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    import org.apache.log4j.Logger;
    
    
    /** 
     * @author TangWenming  E-mail:soonfly#gmail.com
     * @version 创建时间:2017-5-16 下午5:49:17 
     * 类说明 :友元类,包内可见,不提供给客户程序直接访问。
     */
    class ConnectionPool implements IConnectionPool {
    
        private static final Logger log = Logger.getLogger(ConnectionPool.class);
    
        private DBPropertyBean propertyBean=null;
    
        //连接池可用状态
        private Boolean isActive = true;
    
        // 空闲连接池 。
        private Hashtable<Integer,Connection> freeConnections = new Hashtable<Integer,Connection>();  
        // 活动连接池。
        private Hashtable<Integer,Connection> activeConnections = new Hashtable<Integer,Connection>(); 
    
        //当前线程获得的连接
        private ThreadLocal<Connection> currentConnection= new ThreadLocal<Connection>();
    
        //构造方法无法返回null,所以取消掉。在下面增加了CreateConnectionPool静态方法。
        private ConnectionPool(){
            super();
        }
    
        public static ConnectionPool CreateConnectionPool(DBPropertyBean propertyBean) {
            ConnectionPool connpool=new ConnectionPool();
            connpool.propertyBean = propertyBean;
    
            //加载驱动 
    
            //在多节点环境配置下,因为在这里无法判断驱动是否已经加载,可能会造成多次重复加载相同驱动。
            //因此加载驱动的动作,挪到connectionManager管理类中去实现了。
            /*try {
                Class.forName(connpool.propertyBean.getDriverName());
                log.info("加载JDBC驱动"+connpool.propertyBean.getDriverName()+"成功");
            } catch (ClassNotFoundException e) {
                log.info("未找到JDBC驱动" + connpool.propertyBean.getDriverName() + ",请引入相关包");
                return null;
            }*/
    
            //基本点2、始使化时根据配置中的初始连接数创建指定数量的连接
            for (int i = 0; i < connpool.propertyBean.getInitConnections(); i++) {
                try {
                    Connection conn = connpool.NewConnection();
                    connpool.freeConnections.put(conn.hashCode(), conn);
                } catch (SQLException | ClassNotFoundException e) {
                    log.error(connpool.propertyBean.getNodeName()+"节点连接池初始化失败");
                    return null;
                }
            }
    
            connpool.isActive = true;
            return connpool;
        }
    
    
    
        /**
         * 检测连接是否有效
         * @param 数据库连接对象
         * @return Boolean
         */
        private Boolean isValidConnection(Connection conn) throws SQLException{
            try {
                if(conn==null || conn.isClosed()){
                    return false;
                }
            } catch (SQLException e) {
                throw new SQLException(e);
            }
            return true;
        }
    
        /**
         * 创建一个新的连接
         * @return 数据库连接对象
         * @throws ClassNotFoundException
         * @throws SQLException
         */
        private Connection NewConnection() throws ClassNotFoundException,
                SQLException {
    
            Connection conn = null;
            try {
                if (this.propertyBean != null) {
                    //Class.forName(this.propertyBean.getDriverName());
                    conn = DriverManager.getConnection(this.propertyBean.getUrl(),
                            this.propertyBean.getUsername(),
                            this.propertyBean.getPassword());
                }
            } catch (SQLException e) {
                throw new SQLException(e);
            }
    
            if (conn != null) {
                ConProxyHandler handler = new ConProxyHandler(this, conn);
                Connection conn_return = (Connection) Proxy.newProxyInstance(
                        ConnectionPool.class.getClassLoader(),
                        new Class[] { Connection.class }, handler);
                //System.out.println("生成代理类:" + conn_return);
                return conn_return;
            }
    
            return conn;
        }
    
    
        @Override
        public synchronized Connection getConnection() {
            Connection conn = null;
            if (this.getActiveNum() < this.propertyBean.getMaxConnections()) {
                // 分支1:当前使用的连接没有达到最大连接数  
                // 基本点3、在连接池没有达到最大连接数之前,如果有可用的空闲连接就直接使用空闲连接,如果没有,就创建新的连接。
                if (this.getFreeNum() > 0) {
                    // 分支1.1:如果空闲池中有连接,就从空闲池中直接获取
                    int key = (int) this.freeConnections.keySet().toArray()[0];
                    log.info("分支1.1:如果空闲池中有连接,"+Thread.currentThread().getName()+"就从空闲池中直接获取"+key);
                    //conn = this.freeConnections.pollFirst();
    
                    conn = this.freeConnections.get(key);
                    this.freeConnections.remove(key);
    
    
                    //连接闲置久了也会超时,因此空闲池中的有效连接会越来越少,需要另一个进程进行扫描监测,不断保持一定数量的可用连接。
                    //在下面定义了checkFreepools的TimerTask类,在checkPool()方法中进行调用。
    
                    //基本点5、由于数据库连接闲置久了会超时关闭,因此需要连接池采用机制保证每次请求的连接都是有效可用的。
                    try {
                        if(this.isValidConnection(conn)){
                            this.activeConnections.put(conn.hashCode(), conn);
                            currentConnection.set(conn);
                        }else{
                            conn = getConnection();//同步方法是可重入锁
                        }
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
    
                    System.out.println(this.propertyBean.getNodeName() +"空闲连接数:"+getFreeNum());  
                    System.out.println(this.propertyBean.getNodeName() +"活动连接数:"+getActiveNum());
                } else {
    
                    try {
                        conn = this.NewConnection();
                        // 分支1.2:如果空闲池中无可用连接,就创建新的连接
                        log.info("分支1.2:如果空闲池中无可用连接,"+Thread.currentThread().getName()+"就创建新的连接"+conn.hashCode());
                        this.activeConnections.put(conn.hashCode(), conn);
                    } catch (ClassNotFoundException | SQLException e) {
                        e.printStackTrace();
                    }
                    System.out.println(this.propertyBean.getNodeName() +"空闲连接数:"+getFreeNum());  
                    System.out.println(this.propertyBean.getNodeName() +"活动连接数:"+getActiveNum());
                }
            } else {
                // 分支2:当前已到达最大连接数  
                // 基本点4、当连接池中的活动连接数达到最大连接数,新的请求进入等待状态,直到有连接被释放。
                log.info("分支2:"+Thread.currentThread().getName()+":当前已到达最大连接数 ");
                long startTime = System.currentTimeMillis();
    
                //进入等待状态。等待被notify(),notifyALL()唤醒或者超时自动苏醒  
                try{
                    this.wait(this.propertyBean.getConninterval());  
                }catch(InterruptedException e) {  
                    log.error("线程等待被打断");  
                }
    
                //若线程超时前被唤醒并成功获取连接,就不会走到return null。
                //若线程超时前没有获取连接,则返回null。
                //如果timeout设置为0,就无限重连。
                if(this.propertyBean.getTimeout()!=0){
                    if(System.currentTimeMillis() - startTime > this.propertyBean.getTimeout())  
                        return null;  
                }
                conn = this.getConnection();
    
            }
            return conn;
        }
    
    
        @Override
        public Connection getCurrentConnecton() {
            Connection conn=currentConnection.get();
            try {
                if(! isValidConnection(conn)){
                    conn=this.getConnection();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return conn;
        }
    
    
        @Override
        public synchronized void releaseConn(Connection conn) throws SQLException {
    
            log.info(Thread.currentThread().getName()+"关闭连接:activeConnections.remove:"+conn.hashCode());
            this.activeConnections.remove(conn.hashCode());
            this.currentConnection.remove();
            //活动连接池删除的连接,相应的加到空闲连接池中
            try {
                if(isValidConnection(conn)){
                    freeConnections.put(conn.hashCode(), conn);
                }else{
                    Connection newconn = this.NewConnection();
                    freeConnections.put(newconn.hashCode(), newconn);
                }
    
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
            }
            System.out.println(this.propertyBean.getNodeName() +"空闲连接数:"+getFreeNum());  
            System.out.println(this.propertyBean.getNodeName() +"活动连接数:"+getActiveNum());
            //唤醒getConnection()中等待的线程
            this.notifyAll();
        }
    
        @Override
        public synchronized void destroy() {
            for(int key:this.freeConnections.keySet()){
                Connection conn = this.freeConnections.get(key);
                try {
                    if (this.isValidConnection(conn)) { 
                        conn.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            for(int key:this.activeConnections.keySet()){
                Connection conn = this.activeConnections.get(key);
                try {
                    if (this.isValidConnection(conn)) { 
                        conn.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            this.isActive = false;
            this.freeConnections.clear();
            this.activeConnections.clear();
        }
    
        @Override
        public boolean isActive() {
            return this.isActive;
        }
    
    
        @Override
        public void checkPool() {
    
            /*final String nodename=this.propertyBean.getNodeName();
    
            ScheduledExecutorService ses=Executors.newScheduledThreadPool(2);
    
            //功能一:开启一个定时器线程输出状态
            ses.scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    System.out.println(nodename +"空闲连接数:"+getFreeNum());  
                    System.out.println(nodename +"活动连接数:"+getActiveNum());   
    
                }
            }, 1, 1, TimeUnit.SECONDS);
    
            //功能二:开启一个定时器线程,监测并维持空闲池中的最小连接数
            ses.scheduleAtFixedRate(new checkFreepools(this), 1, 5, TimeUnit.SECONDS);*/
        }
    
        @Override
        public synchronized int getActiveNum() {
            return this.activeConnections.size();
        }
    
        @Override
        public synchronized int getFreeNum() {
            return this.freeConnections.size();
        }
    
        //基本点6、连接池内部要保证指定最小连接数量的空闲连接
        class checkFreepools extends TimerTask {
            private ConnectionPool conpool = null;
    
            public checkFreepools(ConnectionPool cp) {
                this.conpool = cp;
            }
    
            @Override
            public void run() {
                if (this.conpool != null && this.conpool.isActive()) {
                    int poolstotalnum = conpool.getFreeNum()
                            + conpool.getActiveNum();
                    int subnum = conpool.propertyBean.getMinConnections()
                            - poolstotalnum;
    
                    if (subnum > 0) {
                        System.out.println(conpool.propertyBean.getNodeName()
                                + "扫描并维持空闲池中的最小连接数,需补充" + subnum + "个连接");
                        for (int i = 0; i < subnum; i++) {
                            try {
                                Connection newconn = conpool.NewConnection();
                                conpool.freeConnections.put(newconn.hashCode(), newconn);
                            } catch (ClassNotFoundException | SQLException e) {
                                e.printStackTrace();
                            }
                        }
    
                    }
                }
    
            }
    
        }
    
        class ConProxyHandler implements InvocationHandler{
            private Connection conn;
            private ConnectionPool cp;
    
            public ConProxyHandler(ConnectionPool cp, Connection conn) {
                this.cp = cp;
                this.conn = conn;
            }
    
            @Override
    
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                if(method.getName().equals("close")){
                    cp.releaseConn(this.conn);
                    return this.conn;
                }else{
                            return true;
                        }
                    }else {
                        return true;
                    }
                }else{
                    //如果不是调用close方法,就原样处理
                    return method.invoke(this.conn, args);
                }
            }
    
        }
    
    }

    再次运行。。。。又出西西了。发现如果某个线程获取到的是另一个线程使用过的connection,就无法调用close方法正常关闭。

    可以把下面这几个参数值改动一下,很快就见效:

    default.maxconnections=2
    default.minconnections=1
    default.initconnections=1

    运行程序后,可以看到最后获取到连接的线程,并没能成功调用con.close()。

    至此,数据库连接池是成功实现了。
    但是最后优化过程中使用代理Connection对象的方式并没有成功。希望大家帮我一起分析一下是什么原因导致的,可以留言,也可站内信给我。主要为了弄明白到底是怎么回事,不甚感激!

    展开全文
  • java开发,利用servlet实现登录及数据库查询
  • java实现文件上传到数据库

    万次阅读 2015-03-30 21:09:15
    背景: 刚入职,到了一个官僚主义严重的公司,接到一个腐败的项目,需要把文本扫描为图片上传到数据库,外面文件夹不留扫描文件电子档。拿到任务后,我查了些资料发现一些思路, 我的解决方案: 1、首先将文件上传到...
    背景: 刚入职,到了一个官僚主义严重的公司,接到一个腐败的项目,需要把文本扫描为图片上传到数据库,外面文件夹不留扫描文件电子档。拿到任务后,我查了些资料发现一些思路,
    我的解决方案:
    1、首先将文件上传到服务器,
    2、将文件传到数据库
    3、上传完成后,删除文件
    

    好了思路就这么样了,开始码代码吧…………
    申明一下,代码已打包(灰常详细)
    http://download.csdn.net/detail/zengshunyao/8548493

    package com.funi.action;
    
    import java.io.File;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Iterator;
    import java.util.List;
    
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.tomcat.util.http.fileupload.FileItem;
    import org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory;
    import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
    import org.apache.tomcat.util.http.fileupload.servlet.ServletRequestContext;
    
    import com.funi.dao.MaterialDao4Oracle;
    import com.funi.exception.FUNIException;
    import com.funi.upload.FUNIFile;
    
    /**
     * Servlet implementation class Upload
     */
    @Deprecated
    @WebServlet(description = "上传文件服务", urlPatterns = { "/uploadServletDemo" })
    public class Upload extends HttpServlet {
        private static final long serialVersionUID = 1L;
    
        private String filePath; // 文件存放目录
        private String tempPath; // 临时文件目录
    
        public Upload() {
            super();
        }
    
        @Override
        public void init(ServletConfig config) throws ServletException {
    
            super.init(config); // 从配置文件中获得初始化参数 filePath =
            config.getInitParameter("filepath");
            tempPath = config.getInitParameter("temppath");
    
            ServletContext context = getServletContext();
    
            filePath = context.getRealPath(filePath);
            tempPath = context.getRealPath(tempPath);
    
            // 如果路径不存在,则创建路径
            File pathFile = new File(filePath);
            File pathTemp = new File(tempPath);
            if (!pathFile.exists()) {
                pathFile.mkdirs();
            }
            if (!pathTemp.exists()) {
                pathTemp.mkdirs();
            }
            System.out.println(filePath);
            System.out.println("文件存放目录、临时文件目录准备完毕 ...");
    
        }
    
        protected void doPost(HttpServletRequest request,
                HttpServletResponse response) throws ServletException, IOException {
    
            PrintWriter pw = response.getWriter();
    
            try {
                DiskFileItemFactory diskFactory = new DiskFileItemFactory();
                // threshold 极限、临界值,即硬盘缓存 1G
                diskFactory.setSizeThreshold(1000 * 1024 * 1024);
                // repository 贮藏室,即临时文件目录
                diskFactory.setRepository(new File(tempPath));
    
                ServletFileUpload upload = new ServletFileUpload(diskFactory);
                // 设置允许上传的最大文件大小 1G upload.setSizeMax(1000 * 1024 * 1024);
                // 解析HTTP请求消息头
                List<FileItem> fileItems = upload
                        .parseRequest(new ServletRequestContext(request));
                Iterator<FileItem> iter = fileItems.iterator();
                while (iter.hasNext()) {
                    FileItem item = (FileItem) iter.next();
                    if (item.isFormField()) {
                        System.out.println("处理表单内容 ...");
                        processFormField(item, pw);
                    } else {
                        System.out.println("处理上传的文件 ...");
                        processUploadFile(item, pw);
                    }
                }// end while()
    
                pw.close();
            } catch (Exception e) {
                System.out.println("使用 fileupload 包时发生异常 ...");
                e.printStackTrace();
            }
    
            try {
                new FUNIFile().upload(request, response, null);
            } catch (FUNIException e) {
    
                e.printStackTrace();
            }
        }
    
        // 处理表单内容
        private void processFormField(FileItem item, PrintWriter pw)
                throws Exception {
            String name = item.getFieldName();
            String value = item.getString();
            pw.println(name + " : " + value + "\r\n");
        }
    
        // 处理上传的文件
        private void processUploadFile(FileItem item, PrintWriter pw)
                throws Exception {
            // 此时的文件名包含了完整的路径,得注意加工一下
            String filename = item.getName();
            System.out.println("完整的文件名:" + filename);
            int index = filename.lastIndexOf("\\");
            filename = filename.substring(index + 1, filename.length());
    
            long fileSize = item.getSize();
    
            if ("".equals(filename) && fileSize == 0) {
                System.out.println("文件名为空 ...");
                return;
            }
    
            File uploadFile = new File(filePath + "/" + filename);
            if (!uploadFile.exists()) {
                uploadFile.createNewFile();
            }
            item.write(uploadFile);
    
            // 写入到数据库
            new MaterialDao4Oracle().add(uploadFile);
            // 完成写入
            uploadFile.delete();//删除文件不留
    
            pw.println(filename + " 文件保存完毕 ...");
            pw.println("文件大小为 :" + fileSize + "\r\n");
        }
    
        protected void doGet(HttpServletRequest request,
                HttpServletResponse response) throws ServletException, IOException {
            this.doPost(request, response);
        }
    }
    

    插入数据库代码

    package com.funi.dao;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.sql.Connection;
    import java.sql.SQLException;
    
    import oracle.jdbc.OracleResultSet;
    import oracle.jdbc.driver.OraclePreparedStatement;
    
    import com.funi.util.DBConn4Oracle;
    
    
    /**
     * 
     * 
     * 
     * @author zengshunyao
     * @version 1.0
     */
    public class MaterialDao4Oracle {
    
        private Connection conn;
    
        public MaterialDao4Oracle() {
            super();
            conn = DBConn4Oracle.getConn();
        }
    
    public InputStream find(int id) throws SQLException, IOException {
            String selectSql = "select pic from  IMAGEDETAIL  where  id=?";
            OraclePreparedStatement ps = (OraclePreparedStatement) conn
                    .prepareStatement(selectSql);
            ps.setInt(1, id);
    
            OracleResultSet rs = (OracleResultSet) ps.executeQuery();
            InputStream ins = null;
            if (rs.next()) {
                java.sql.Blob blob = rs.getBlob("pic");
                ins = blob.getBinaryStream();
            }
            return ins;
        }
    //
    //  public Blob getBlob(int id) throws SQLException, IOException {
    //      String selectSql = "select pic from  IMAGEDETAIL  where  id=?";
    //      OraclePreparedStatement ps = (OraclePreparedStatement) conn
    //              .prepareStatement(selectSql);
    //      ps.setInt(1, id);
    //
    //      OracleResultSet rs = (OracleResultSet) ps.executeQuery();
    //      java.sql.Blob blob = null;
    //      if (rs.next()) {
    //          blob = rs.getBlob("pic");
    //      }
    //      return blob;
    //  }
    
        public int add(File file) throws IOException, SQLException {
            OraclePreparedStatement ps = null;
            OracleResultSet rs = null;
            FileInputStream fin = null;
            int influencenum = -1;
            // if (conn.getAutoCommit())
            // 此处不建议判断,看到源码后,你就知道了(全是线程安全[synchronized])
            conn.setAutoCommit(false);// 设置事务手动提交
    
            String inertSql = "insert into IMAGEDETAIL(id,pic) values(?,empty_blob())";
            String selectSql = "select pic from IMAGEDETAIL where id = ? for update";
    
            ps = (OraclePreparedStatement) conn.prepareStatement(inertSql);
            ps.setInt(1, 3);
            influencenum = ps.executeUpdate();
    
            ps = (OraclePreparedStatement) conn.prepareStatement(selectSql);
            ps.setInt(1, 3);
            rs = (OracleResultSet) ps.executeQuery();
    
            if (rs.next()) {
                oracle.sql.BLOB blob = (oracle.sql.BLOB) rs.getBlob("pic");
                OutputStream out = blob.getBinaryOutputStream();
                byte[] b = new byte[blob.getBufferSize()];
                int len = 0;
                fin = new FileInputStream(file);
                while ((len = fin.read(b)) != -1)
                    out.write(b, 0, len);
                fin.close();
                out.flush();
                out.close();
                conn.commit();
                rs.close();
                // conn.close();
            }
            return influencenum;
        }
    }
    

    然后是单例模式的获得数据库连接

            package com.funi.util;
         import java.sql.Connection;
    

    import java.sql.DriverManager;
    import java.sql.SQLException;

    /**
    *
    * @author zengshunyao
    * @version 1.0
    */
    public class DBConn4Oracle {

    // 驱动程序名
    private final String driver = "oracle.jdbc.driver.OracleDriver";
    
    // URL指向要访问的数据库名scutcs
    private final String url = "jdbc:oracle:thin:@localhost:1521:orcl";
    
    // MySQL配置时的用户名
    private final String user = "funi";
    
    // Java连接MySQL配置时的密码
    private static final String password = "yourpassword";
    
    private static Connection conn = null;
    
    private DBConn4Oracle() {
        try {
            // 加载驱动程序
            Class.forName(driver);
            // 连续数据库
            conn = DriverManager.getConnection(url, user, password);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    public static final Connection getConn() {
        if (conn == null) {
            synchronized (DBConn4Oracle.class) {
                if (conn == null) {
                    new DBConn4Oracle();
                }
            }
        }
        try {
            if (!conn.isClosed())
                System.out.println("Succeeded connecting to the Database!");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
    
    public static void main(String[] args) {
        System.out.println(DBConn4Oracle.getConn());
    }
    

    }

    最后就是jsp了

    <form name="uploadForm" method="post" enctype="multipart/form-data"
            action="UP">
            Name:<input type="text" name="username" /> <br /> File1:<input
                type="file" name="file1" /> <br /> File2:<input type="file"
                name="file2" /> <br /> <input type="submit" name="submit"
                value="上传"> <input type="reset" name="reset" value="重置">
        </form>

    比较值得注意的几个地方

    1。db.conn.setAutoCommit(false); 需要将自动提交设置为false。否则会报一个
    java.sql.SQLException: ORA-01002: fetch out of sequence的错误。

    2。要存入文件需要oracle用blob字段,并且注意Oracle的 BLOB一定要用EMPTY_BLOB()初始化
    在insert之后,要select原来的文件应该用for update
    比如:select filebody from filetest where filename=? for update
    否则会报 ORA-22920: row containing the LOB value is not locked的错误。

    其他基本OK

    代码打包http://download.csdn.net/detail/zengshunyao/8548493
    随便转载,切记留下出处http://blog.csdn.net/zengshunyao/article/details/44757761

    展开全文
  • Java实现 接口请求频率限制方案源码

    千次阅读 2020-09-15 20:26:19
    实际工作中在某些业务场景会遇到一个问题,用户疯狂点击某个按钮功能导致接口在短时间内被重复请求多次,且这些重复请求在业务上是无效的请求,这个问题即会导致后端出现大量重复无效或错误的数据,也会对后端服务器...

    实际工作中在某些业务场景会遇到一个问题,用户疯狂点击某个按钮功能导致接口在短时间内被重复请求多次,且这些重复请求在业务上是无效的请求,这个问题即会导致后端出现大量重复无效或错误的数据,也会对后端服务器等造成影响。

    解决方案需要从服务端的部署方式出发

    1.集群部署(如果nginx等转发服务器采用按IP或SESSION_ID等方式的转发策略也可使用单机部署的方案):集群部署的情况推荐使用redis分布式锁进行处理,具体代码方案参考之前的文章:

    Java+RedisTemplate 实现redis分布式锁

    2.单机部署

    单机部署方案适合小型项目采用,直接使用Java代码实现不依赖第三方

    核心:ConcurrentMap + DelayQueue

    实现demo如下

    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ConcurrentMap;
    import java.util.concurrent.DelayQueue;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 请求唯一值存放类
     */
    public class RequestLimitCache<K, V> {
        //存放每个用户请求的唯一值
    	private ConcurrentMap<K, V> cacheObjMap = new ConcurrentHashMap<>();
        //延迟队列,用于倒计时结束后推出请求唯一值
    	private DelayQueue<DelayItem<Pair<K, V>>> queue = new DelayQueue<>();
        //守护线程
    	private Thread daemonThread;
        //初始化
    	public RequestLimitCache() {
    
    		Runnable daemonTask = new Runnable() {
    			@Override
    			public void run() {
    				daemonCheck();
    			}
    		};
    		daemonThread = new Thread(daemonTask);
    		daemonThread.setDaemon(true);
    		daemonThread.setName("LimitCache Daemon");
    		daemonThread.start();
    	}
        //保持一个线程始终持有延迟队列的监视任务,并处理超时的任务内容
    	private void daemonCheck() {
    		while (true) {
    			try {
    				DelayItem<Pair<K, V>> delayItem = queue.take();
    				if (delayItem != null) {
    					// 超时对象处理
    					Pair<K, V> pair = delayItem.getItem();
    					cacheObjMap.remove(pair.k, pair.v);
    				} else {
    					Thread.sleep(500);
    				}
    			} catch (InterruptedException e) {
    				break;
    			}
    		}
    	}
        // 存入队列方法
    	public void put(K key, V value, long time, TimeUnit unit) {
    		V oldValue = cacheObjMap.put(key, value);
    		if (oldValue != null) {
    			queue.remove(key);
    		}
    		long nanoTime = TimeUnit.MILLISECONDS.convert(time, unit);
    		queue.put(new DelayItem<>(new Pair<>(key, value), nanoTime));
    	}
        // 取出方法
    	public V get(K key) {
    		return cacheObjMap.get(key);
    	}
    }
    import java.util.concurrent.TimeUnit;
    
    /**
     * 单机锁实现类
     */
    @Component
    public class RequestLimit {
    
    	private RequestLimitCache<String, Boolean> requestLimitCache = new RequestLimitCache<>();
        // 默认限制时间
    	private long liveTime = 1000 * 1;
    
        // 自定义限制时间
    	public void checkFast(String key, long liveTime) {
    		Boolean b = requestLimitCache.get(key);
    		if (b != null && b) {
    			throw new Exception(”requestLimit“);
    		}
    		requestLimitCache.put(key, true, liveTime*1000, TimeUnit.MILLISECONDS);
    	}
        // 使用默认限制时间
    	public void checkFast(String key) {
    		checkFast(key, liveTime);
    	}
    
    }

    最后附上调用方法:

    @Autowired
    private RequestLimit requestLimit;
    // 使用默认限制时间
    requestLimit.checkFast("业务名称key" + AppContext.getHttpServletRequest().getSession().getId());
    // 自定义请求限制时间
    requestLimit.checkFast("业务名称key" + AppContext.getHttpServletRequest().getSession().getId(),2);
    
    展开全文
  • Java如何实现对Mysql数据库的行锁

    万次阅读 2017-08-24 20:15:39
    1. 使用共享锁线程可对其锁定记录进行读取,其他线程同样也可对锁定记录进行读取操作,并且这两个线程读取的数据都属于同一个版本。 2. 对于写入操作,使用共享锁的线程需要分情况讨论,当只有当前线程对指定...
  • 本文将利用一个简单的登录评论功能,完整介绍mybatis框架的使用。 实现功能:对数据库的增删改查,多表查询,模糊查询,动态查询
  • 微信小程序实现登录Java前后端分离

    千次阅读 2020-02-05 10:44:17
    找了很多资料虽然文档很多,但是自己需要的的并不多,现在我把自己学会的做一个小小的demo,以便给那些还没有经验的入门者一些小小的指引,我这是一篇基于Java后端的前后端分离的微信小程序实现的登录。鉴于是简单的...
  • Java框架总结

    万次阅读 多人点赞 2020-01-17 14:14:13
    本系列用来记录常用java...、SSH 1、基本概念 SSH框架是JAVA EE中三种框架所集成,分别是Struts,Spring,Hibernate框架所组成,是当前比较流行的java web开源框架。 集成SSH框架的系统从职责上分为(Struts2--...
  • Java数据库连接池实现原理

    万次阅读 多人点赞 2016-05-29 16:33:02
    一般来说,Java应用程序访问数据库的过程是:  ①装载数据库驱动程序;  ②通过jdbc建立数据库连接;  ③访问数据库,执行sql语句;  ④断开数据库连接。 public class DBConnection { private Connection ...
  • java 仓库管理系统源码

    万次阅读 多人点赞 2020-02-03 21:57:13
    系统提供基本的登入登出功能,同时系统包含两角色:系统超级管理员和普通管理员,超级管理员具有最高的操作权限,而普通管理员仅具有最基本的操作权限,而且仅能操作自己被指派的仓库。 请求URL鉴权。对于系统...
  • JAVA上百实例源码以及开源项目

    千次下载 热门讨论 2016-01-03 17:37:40
     当用户发送第一次请求的时候,验证用户登录,创建一个该qq号和服务器端保持通讯连接得线程,启动该通讯线程,通讯完毕,关闭Scoket。  QQ客户端登录界面,中部有三个JPanel,有一个叫选项卡窗口管理。还可以更新...
  • java数据库连接池实现原理

    万次阅读 多人点赞 2014-05-10 10:53:53
     一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的 性能低下。 数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个...
  • Java-一次请求在同一个事务实现

    千次阅读 2013-04-23 08:01:47
    HiberSessionFilter过滤器代码: package ... import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.s
  • java通过jdbc连接Oracle通过数据库连接池实现增删改查
  • 、前台 1.上传页面 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> Insert title here 上传文件1: 2、下载页面原理同上 二、上传 package ...
  • Java连接数据库实现用户登录密码验证

    万次阅读 多人点赞 2019-05-07 21:23:59
    1.在store_db数据库中创建一个t_user表; 在domain包中创建一个User的类: package com.xxf.domain; public class User { private Integer id; private String username; private String password; private ...
  • java实现多文件上传并保存到数据库

    千次阅读 2018-07-25 15:57:43
    好久没有写博客了,今天项目里需要一个功能,即前台实现多图片上传列表,并后台保存到mysql中。前台后台一起弄,用了半天时间终于搞定了。 实现:前台Layui实现;后台servlet+hibernate 不多废话,先上图 ...
  • 实现软件 IntelliJ IDEA 2019.2 ...3.创建一个 java web 项目并导入 jar 包 mysql-connector-java-5.1.9-bin.jar 1.创建Dao类,负责数据库的连接与关闭 package czh;// import java.sql.*; public class D...
  • 手把手教你用Java设计并实现一个城市公交查询系统

    千次阅读 多人点赞 2020-12-19 10:11:33
    为了使得我国公交乘客出行及查询有关信息更方便,本文运用JAVA语言技术,Jsp技术,Mysql数据库开发了B/S结构的城市公交查询系统。 该系统顺应了时代发展且具有以下优点:首先,方便乘客的出行,乘客不用询问站牌工作...
  • Java如何发起http请求

    千次阅读 多人点赞 2021-02-22 21:01:06
    Java如何发起http请求前言、GET与POST1.GET方法2.POST方法实现代码实例演示字符串转json结束 前言 在未来做项目中,一些功能模块可能会采用不同的语言进行编写。这就需要http请求进行模块的调用。那么下面,我将...
  • 微信小程序通过JAVA连接数据库

    千次阅读 热门讨论 2019-05-17 21:09:19
    微信小程序通过JAVA连接数据库了解微信小程序的数据请求微信小程序传输数据后端接受并传回数据接收数据返回数据小程序接收数据最后 了解微信小程序的数据请求 官方的开发文档中有对应的网络请求: ...
  • Java 学习路线

    万次阅读 多人点赞 2018-01-06 13:21:35
    对于入门java将近两年的时间,曾经迷惘过,一直想知道java的具体学习路线,看过了许许多多的java经验分享的帖子,评论,以及其他各种培训机构所谓的学习路线,发现没有一个符合我个人需求的学习路线,根据个人实际的...
  • poi操作Excel 主要通过HSSF,XSSF两种方式。 HSSF只能解析.xls格式的excel文件,XSSF支持....2.根据excel文件格式解析excel文件,读取excel每行数据并存放到集合里 3.批量写入数据库 import java.io.IOException; i...
  • 通过java代码如何实现数据库的增删改查,怎么理解Driver?具体的实现过程请求大神解答-jdbc
  • 史上最全面Java面试汇总(面试题+答案)

    万次阅读 多人点赞 2018-07-06 14:09:25
    JAVA面试精选【Java基础第部分】 JAVA面试精选【Java基础第二部分】 JAVA面试精选【Java基础第三部分】 JAVA面试精选【Java算法与编程JAVA面试精选【Java算法与编程二】 Java高级工程师—面试(1) ...
  • Java 爬取信息存入数据库

    千次阅读 2017-05-23 20:50:35
    功能包括抓取新闻信息,保存在数据库中,并根据请求返回相应数据。这里我选取的新闻来源是一点资讯(主要是他的API比较容易获取),抓取和解析选取Jsoup+Gson.运行采取Schedul 定时运行爬虫脚本。
  • //连接数据库 Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver"); //得到连接 ct=DriverManager.getConnection("jdbc:microsoft:sqlserver://127.0.0.1:1433;databaseName=spdb","sa...
  • 这通过java.lang.Class类的静态方法forName(String className)实现 成功加载后,会将Driver类的实例注册到DriverManager类中 二、提供JDBC连接的URL: 连接URL定义了连接数据库时的协议、子协议、数据源标识 书写...
  • 因多个应用之间并非同一个jvm(应用)内,因此使用synchronized并不能满足需求。 具体处理方案包含以下几种: 1)数据库行级索,优点:简单粗暴;缺点:容易死锁&amp;性能差,非数据库专业人士不建议使用。 2...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 488,489
精华内容 195,395
关键字:

java实现一个请求数据库

java 订阅