Java连接数据库 #02# JDBC经典套路

内容索引

  • LocalConnectionFactory.java
  • LocalConnectionProxy.java
  • ProfileDAO.java-2.0
  • ProfileDAOImpl.java-2.0
  • DaoException.java
  • DbUtil.java-2.0
  • ProfileDAOTest.java-2.0
  • DaoTest.java(并发测试)

Java连接数据库 #01# JDBC单线程适用,主要是改为多线程适用,顺便对DAO层结构进行改良:

  1. connection由共享变量改为由一个ThreadLocal变量保存
  2. 添加一个connection的管理类来控制事务
  3. dao层统一向上抛出DaoException
  4. dao层对象改为单例

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

① LocalConnectionFactory.java 负责创建connection/用ThreadLocal变量保存connection

package org.sample.db;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ResourceBundle;

public class LocalConnectionFactory {

    private LocalConnectionFactory() {
        // Exists to defeat instantiation
    }

    private static ResourceBundle rb = ResourceBundle.getBundle("org.sample.db.db-config");

    private static final String JDBC_URL = rb.getString("jdbc.url");

    private static final String JDBC_USER = rb.getString("jdbc.username");

    private static final String JDBC_PASSWORD = rb.getString("jdbc.password");

    private static final ThreadLocal<Connection> LocalConnectionHolder = new ThreadLocal<>();

    public static Connection getConnection() throws SQLException {
        Connection conn = LocalConnectionHolder.get();
        if (conn == null || conn.isClosed()) {
            conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
            LocalConnectionHolder.set(conn);
        }
        return conn;
    }

    public static void removeLocalConnection() {
        LocalConnectionHolder.remove();
    }
}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

② LocalConnectionProxy.java 通过该类间接控制connection,在Service层控制事务

package org.sample.manager;

import org.sample.db.LocalConnectionFactory;
import org.sample.exception.DaoException;

import java.sql.Connection;
import java.sql.SQLException;

public class LocalConnectionProxy {

    public static void setAutoCommit(boolean autoCommit) throws DaoException {
        try {
            Connection conn = LocalConnectionFactory.getConnection();
            conn.setAutoCommit(autoCommit);
        } catch (SQLException e) {
            throw new DaoException(e);
        }
    }

//    public static void setTransactionIsolation(int level) throws DaoException {
//        try {
//            Connection conn = LocalConnectionFactory.getConnection();
//            conn.setTransactionIsolation(level);
//        } catch (SQLException e) {
//            throw new DaoException(e);
//        }
//    }

    public static void commit() throws DaoException {
        try {
            Connection conn = LocalConnectionFactory.getConnection();
            conn.commit();
        } catch (SQLException e) {
            throw new DaoException(e);
        }
    }

    public static void rollback() throws DaoException {
        try {
            Connection conn = LocalConnectionFactory.getConnection();
            conn.rollback();
        } catch (SQLException e) {
            throw new DaoException(e);
        }
    }

    public static void close() throws DaoException {
        try {
            Connection conn = LocalConnectionFactory.getConnection();
            conn.close();
            LocalConnectionFactory.removeLocalConnection();
        } catch (SQLException e) {
            throw new DaoException(e);
        }
    }
}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

③ ProfileDAO.java-2.0 标示出了可能抛出的异常

package org.sample.dao;

import org.sample.entity.Profile;
import org.sample.exception.DaoException;

import java.util.List;

public interface ProfileDAO {

    int saveProfile(Profile profile);

    List<Profile> listProfileByNickname(String nickname);

    Profile getProfileByUsername(String username);

    int updateProfileById(Profile profile);

    int updatePassword(String username, String password);

    int updateLastOnline(String username);
}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

④ ProfileDAOImpl.java-2.0 改进了savePOJO方法,该类是线程安全的,并且适合采用单例模式。

package org.sample.dao.impl;

import org.sample.dao.ProfileDAO;
import org.sample.db.LocalConnectionFactory;
import org.sample.entity.Profile;
import org.sample.exception.DaoException;
import org.sample.util.DbUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class ProfileDAOImpl implements ProfileDAO {

    public static final ProfileDAO INSTANCE = new ProfileDAOImpl();

    private ProfileDAOImpl() {}

    @Override
    public int saveProfile(Profile profile) {
        int i = 0;
        try {
            Connection conn = LocalConnectionFactory.getConnection();
            String sql = "INSERT ignore INTO `profiles`.`profile` (`username`, `password`, `nickname`) " +
                    "VALUES (?, ?, ?)"; // 添加ignore出现重复不会抛出异常而是返回0
            try (PreparedStatement ps = conn.prepareStatement(sql)) {
                ps.setString(1, profile.getUsername());
                ps.setString(2, profile.getPassword());
                ps.setString(3, profile.getNickname());
                i = ps.executeUpdate();
            }
        } catch (SQLException e) {
            throw new DaoException(e);
        }
        return i;
    }

    @Override
    public List<Profile> listProfileByNickname(String nickname) {
        List<Profile> result = new ArrayList<>();
        try {
            Connection conn = LocalConnectionFactory.getConnection();
            String sql = "SELECT  `profile_id`,  `username`,  `password`,  `nickname`,  `last_online`,  `gender`,  `birthday`,  `location`,  `joined`" +
                    "FROM `profiles`.`profile`" +
                    "WHERE `nickname`=?";
            try (PreparedStatement ps = conn.prepareStatement(sql)) {
                ps.setString(1, nickname);
                try (ResultSet rs = ps.executeQuery()) {
                    while (rs.next()) {
                        Profile profile = DbUtil.extractProfileFromResultSet(rs);
                        result.add(profile);
                    }
                }
            }
        } catch (SQLException e) {
            throw new DaoException(e);
        }
        return result;
    }

    @Override
    public Profile getProfileByUsername(String username) {
        Profile result = null;
        try {
            Connection conn = LocalConnectionFactory.getConnection();
            String sql = "SELECT  `profile_id`,  `username`,  `password`,  `nickname`,  `last_online`,  `gender`,  `birthday`,  `location`,  `joined`" +
                    "FROM `profiles`.`profile`" +
                    "WHERE `username`=?";
            try (PreparedStatement ps = conn.prepareStatement(sql)) {
                ps.setString(1, username);
                try (ResultSet rs = ps.executeQuery()) {
                    if (rs.next()) {
                        result = DbUtil.extractProfileFromResultSet(rs);
                    }
                }
            }
        } catch (SQLException e) {
            throw new DaoException(e);
        }
        return result;
    }

    @Override
    public int updateProfileById(Profile profile) {
        int i = 0;
        try {
            Connection conn = LocalConnectionFactory.getConnection();
            String sql = "UPDATE `profiles`.`profile`" +
                    "SET `nickname`=?,  `gender`=?,  `birthday`=?,  `location`=? " +
                    "WHERE  `profile_id`=?";
            try (PreparedStatement ps = conn.prepareStatement(sql)) {
                ps.setString(1, profile.getNickname());
                ps.setString(2, profile.getGender() != null ? String.valueOf(profile.getGender()) : null);
                ps.setTimestamp(3, profile.getBirthday());
                ps.setString(4, profile.getLocation());
                ps.setLong(5, profile.getProfileId());
                i = ps.executeUpdate();
            }
        } catch (SQLException e) {
            throw new DaoException(e);
        }
        return i;
    }

    @Override
    public int updatePassword(String username, String password) {
        int i = 0;
        try {
            Connection conn = LocalConnectionFactory.getConnection();
            String sql = "UPDATE `profiles`.`profile`" +
                    "SET `password`=? " +
                    "WHERE  `username`=?";
            try (PreparedStatement ps = conn.prepareStatement(sql)) {
                ps.setString(1, password);
                ps.setString(2, username);
                i = ps.executeUpdate();
            }
        } catch (SQLException e) {
            throw new DaoException(e);
        }
        return i;
    }

    @Override
    public int updateLastOnline(String username) {
        int i = 0;
        try {
            Connection conn = LocalConnectionFactory.getConnection();
            String sql = "UPDATE `profiles`.`profile`" +
                    "SET `last_online`=CURRENT_TIMESTAMP " +
                    "WHERE  `username`=?";
            try (PreparedStatement ps = conn.prepareStatement(sql)) {
                ps.setString(1, username);
                i = ps.executeUpdate();
            }
        } catch (SQLException e) {
            throw new DaoException(e);
        }
        return i;
    }
}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

⑤ DaoException.java(RuntimeException)

package org.sample.exception;

public class DaoException extends OnesProfileException {

    public DaoException() {
        super();
    }

    public DaoException(String message) {
        super(message);
    }

    public DaoException(String message, Throwable cause) {
        super(message, cause);
    }

    public DaoException(Throwable cause) {
        super(cause);
    }

    protected DaoException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

⑥DbUtil.java-2.0 改为一个单纯的数据库操作辅助类。。。

package org.sample.util;

import org.sample.entity.Profile;

import java.sql.ResultSet;
import java.sql.SQLException;

public class DbUtil {

    public static Profile extractProfileFromResultSet(ResultSet rs) throws SQLException {
        Profile profile = new Profile();
        profile.setBirthday(rs.getTimestamp("birthday"));
        profile.setJoined(rs.getTimestamp("joined"));
        profile.setLast_online(rs.getTimestamp("last_online"));
        profile.setLocation(rs.getString("location"));
        profile.setNickname(rs.getString("nickname"));
        profile.setPassword(rs.getString("password"));
        profile.setProfileId(rs.getLong("profile_id"));
        profile.setUsername(rs.getString("username"));
        if (rs.getString("gender") != null) {
            profile.setGender(rs.getString("gender").charAt(0));
        }
        return profile;
    }
}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

⑦ ProfileDAOTest.java-2.0 各个方法测试之间不再相互依赖

package org.sample.dao;

import org.junit.Test;
import org.sample.dao.impl.ProfileDAOImpl;
import org.sample.entity.Profile;
import org.sample.manager.LocalConnectionProxy;

import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class ProfileDAOTest {

    private static final ProfileDAO PROFILE_DAO = ProfileDAOImpl.INSTANCE;

    private static final String ORIGIN_STRING = "hello";
    private static final String PASSWORD = ORIGIN_STRING;

    private static String RandomString() {
        return Math.random() + ORIGIN_STRING + Math.random();
    }

    private static Profile RandomProfile() {
        Profile profile = new Profile(RandomString(), PASSWORD, RandomString());
        return profile;
    }

    @Test
    public void saveProfile() throws Exception {
        Profile profile = RandomProfile();
        int i = PROFILE_DAO.saveProfile(profile);
        int j = PROFILE_DAO.saveProfile(profile);
        LocalConnectionProxy.close();

        assertEquals(1, i);
        assertEquals(0, j);
    }

    @Test
    public void listProfileByNickname() throws Exception {
        final String nickName = RandomString();
        Profile profile1 = new Profile(RandomString(), PASSWORD, nickName);
        Profile profile2 = new Profile(RandomString(), PASSWORD, nickName);
        Profile profile3 = new Profile(RandomString(), PASSWORD, nickName);
        PROFILE_DAO.saveProfile(profile1);
        PROFILE_DAO.saveProfile(profile2);
        PROFILE_DAO.saveProfile(profile3);
        List result = PROFILE_DAO.listProfileByNickname(nickName);
        LocalConnectionProxy.close();

        assertEquals(3, result.size());
    }

    @Test
    public void getProfileByUsername() throws Exception {
        Profile profile = RandomProfile();
        PROFILE_DAO.saveProfile(profile);
        Profile result = PROFILE_DAO.getProfileByUsername(profile.getUsername());
        LocalConnectionProxy.close();

        assertNotNull(result);
    }

    @Test
    public void updateProfileById() throws Exception {
        Profile profile = RandomProfile();
        PROFILE_DAO.saveProfile(profile);
        Profile temp = PROFILE_DAO.getProfileByUsername(profile.getUsername());
        int i = PROFILE_DAO.updateProfileById(temp);

        LocalConnectionProxy.close();

        assertEquals(1, i);
    }

    @Test
    public void updatePassword() throws Exception {
        Profile profile = RandomProfile();
        PROFILE_DAO.saveProfile(profile);
        int i = PROFILE_DAO.updatePassword(profile.getUsername(), RandomString());
        LocalConnectionProxy.close();

        assertEquals(1, i);
    }

    @Test
    public void updateLastOnline() throws Exception {
        Profile profile = RandomProfile();
        PROFILE_DAO.saveProfile(profile);
        int i = PROFILE_DAO.updateLastOnline(profile.getUsername());
        LocalConnectionProxy.close();

        assertEquals(1, i);
    }

}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

⑧ DaoTest.java 简单测试了下在并发情况下代码的运行状况

package org.sample.manager;

import org.junit.Test;
import org.sample.dao.ProfileDAO;
import org.sample.dao.impl.ProfileDAOImpl;
import org.sample.entity.Profile;
import org.sample.exception.DaoException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import static org.junit.Assert.assertTrue;

public class DaoTest {

    private static final Logger LOGGER = Logger.getLogger(DaoTest.class.getName());

    private static final String ORIGIN_STRING = "hello";
    private static String RandomString() {
        return Math.random() + ORIGIN_STRING + Math.random();
    }
    private static Profile RandomProfile() {
        Profile profile = new Profile(RandomString(), ORIGIN_STRING, RandomString());
        return profile;
    }

    private static final ProfileDAO PROFILE_DAO = ProfileDAOImpl.INSTANCE;

    private class Worker implements Runnable {
        private final Profile profile = RandomProfile();

        @Override
        public void run() {
            LOGGER.info(Thread.currentThread().getName() + " has started his work");
            try {
                LocalConnectionProxy.setAutoCommit(false);
                PROFILE_DAO.saveProfile(profile);
                LocalConnectionProxy.commit();
            } catch (DaoException e) {
                e.printStackTrace();
            } finally {
                try {
                    LocalConnectionProxy.close();
                } catch (DaoException e) {
                    e.printStackTrace();
                }
            }
            LOGGER.info(Thread.currentThread().getName() + " has finished his work");
        }
    }

    private static final int numTasks = 100;

    @Test
    public void test() throws Exception {
        List<Runnable> workers = new LinkedList<>();
        for(int i = 0; i != numTasks; ++i) {
            workers.add(new Worker());
        }
        assertConcurrent("Dao test ", workers, Integer.MAX_VALUE);
    }

    public static void assertConcurrent(final String message, final List<? extends Runnable> runnables, final int maxTimeoutSeconds) throws InterruptedException {
        final int numThreads = runnables.size();
        final List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<Throwable>());
        final ExecutorService threadPool = Executors.newFixedThreadPool(numThreads);
        try {
            final CountDownLatch allExecutorThreadsReady = new CountDownLatch(numThreads);
            final CountDownLatch afterInitBlocker = new CountDownLatch(1);
            final CountDownLatch allDone = new CountDownLatch(numThreads);
            for (final Runnable submittedTestRunnable : runnables) {
                threadPool.submit(new Runnable() {
                    public void run() {
                        allExecutorThreadsReady.countDown();
                        try {
                            afterInitBlocker.await();
                            submittedTestRunnable.run();
                        } catch (final Throwable e) {
                            exceptions.add(e);
                        } finally {
                            allDone.countDown();
                        }
                    }
                });
            }
            // wait until all threads are ready
            assertTrue("Timeout initializing threads! Perform long lasting initializations before passing runnables to assertConcurrent", allExecutorThreadsReady.await(runnables.size() * 10, TimeUnit.MILLISECONDS));
            // start all test runners
            afterInitBlocker.countDown();
            assertTrue(message +" timeout! More than" + maxTimeoutSeconds + "seconds", allDone.await(maxTimeoutSeconds, TimeUnit.SECONDS));
        } finally {
            threadPool.shutdownNow();
        }
        assertTrue(message + "failed with exception(s)" + exceptions, exceptions.isEmpty());
    }
}

在连接数量超出100时会抛出 com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Data source rejected establishment of connection,  message from server: "Too many connections" ,这是因为超出了mysql默认的最大连接数,这个值是可以自定义的,应根据具体的并发量、服务器性能、业务场景等各种因素综合决定。参考连接池最大/最小连接数,自动释放时间,设置多少合适?

★查看数据库连接相关信息:

mysql> show status like ‘Threads%‘;
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_cached    | 8     |
| Threads_connected | 1     |
| Threads_created   | 3419  |
| Threads_running   | 1     |
+-------------------+-------+
4 rows in set (0.00 sec)

Threads_cached是数据库缓存的连接数。

★查看最大连接数:

mysql> show variables like ‘%max_connections%‘;
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 100   |
+-----------------+-------+
1 row in set (0.01 sec)
mysql> show global status like ‘Max_used_connections‘; ps. 官方解释 - The maximum number of connections that have been in use simultaneously since the server started.
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| Max_used_connections | 101   |
+----------------------+-------+
1 row in set (0.00 sec)

但是我很奇怪为什么max_used_connections会大于max_connections,查文档结果如下 ↓

mysqld actually permits max_connections+1 clients to connect. The extra connection is reserved for use by accounts that have theCONNECTION_ADMIN or SUPER privilege. By granting the SUPERprivilege to administrators and not to normal users (who should not need it), an administrator can connect to the server and use SHOW PROCESSLIST to diagnose problems even if the maximum number of unprivileged clients are connected. See Section 13.7.6.29, “SHOW PROCESSLIST Syntax”.

顺便mark 10 MySQL variables that you should monitor - TechRepublic

原文地址:https://www.cnblogs.com/xkxf/p/9426140.html

时间: 2024-11-01 10:26:26

Java连接数据库 #02# JDBC经典套路的相关文章

Java连接数据库 #01# JDBC单线程适用

官方教程(包括 javase的基础部分):JDBC Basics 重新梳理.学习一下“Java连接数据库”相关的内容. 因为最开始没有认真学多线程和JDBC,一直在自己写的多线程程序中维持下面的错误写法: 多个线程共用一个connection connection只开不关 为什么上述做法是错误的呢? 可以参看这个帖子.- - “JDBC规范并未规定那三个对象必须是线程安全的,因此所有的JDBC厂商也不会去弄成线程安全的,正因为如此,所以就会有并发问题.” .-- “ 并不是说不能把连接对象弄成成

java连接数据库时jdbc设置编码

发现网上很多坑爹货,有很多意思表达不全,比如jdbc连接mysql数据库的时候,设置字符集编码 可以如下配置,mysql可直接在url后面加上字符集设置: ...省略 String url = "jdbc:mysql://localhost:3306/exceltest1?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull"; String username = "

JAVA连接数据库 #03# HikariCP

索引 为什么用数据库连接池? HikariCP快速入门 依赖 简单的草稿程序 设置连接池参数(只列举常用的) MySQL配置 修改Java连接数据库 #02#中的代码 测试 为什么用数据库连接池? 为什么要用数据库连接池? 如果我们分析一下典型的[连接数据库]所涉及的步骤,我们将理解为什么: 使用数据库驱动程序打开与数据库的连接 打开TCP套接字以读取/写入数据 通过套接字读取/写入数据 关闭连接 关闭套接字 很明显,[连接数据库]是相当昂贵的操作,因此,应该想办法尽可能地减少.避免这种操作.

完整java开发中JDBC连接数据库代码和步骤

完整java开发中JDBC连接数据库代码和步骤 JDBC连接数据库 •创建一个以JDBC连接数据库的程序,包含7个步骤: 1.加载JDBC驱动程序: 在连接数据库之前,首先要加载想要连接的数据库的驱动到JVM(Java虚拟机), 这通过java.lang.Class类的静态方法forName(String  className)实现. 例如: try{ //加载MySql的驱动类 Class.forName("com.mysql.jdbc.Driver") ; }catch(Class

java中使用JDBC连接数据库的步骤

java中使用JDBC连接数据库的步骤? 采纳率:46%6级2015.06.30 1.注册驱动Class.forname("com.mysql.jdbc.Driver");//这是连接mysql数据库的驱动2.获取数据库连接java.sql.Connection conn=java.sql.DriverManager.getConnection(); 3.获取表达式java.sql.Statement stmt=conn.createStatement("jdbc:mysql

Java学习资料-Java开发中JDBC连接数据库代码和步骤

完整java开发中JDBC连接数据库代码和步骤:http://www.cnblogs.com/hongten/archive/2011/03/29/1998311.html

Java连接数据库jdbc

java连接数据库 import java.sql.*;public class Jdbc05 { static final String URL="jdbc:mysql://localhost:3306/test"; static final String USER="root"; static final String PWD="root"; public static void main(String[] args) { //定义sql语句

Java学习:JDBC

jdbc简介 DBC(Java Database Connectivity)是一个独立于特定数据库管理系统.通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,使用这个类库可以以一种标准的方法.方便地访问数据库资源. jdbc的核心API Driver接口:数据库驱动程序的接口,所有的数据库厂商需要的驱动程序需要实现此接口. Connection接口:与具体的数据库连接对象: Statement createStatement()    创建一个静态sq

理解JNDI中 java:comp/env/jdbc/datasource 与 jdbc/datasource 的不同之处(转)

在描述JNDI,例如获得数据源时,JNDI地址有两种写法,例如同是  jdbc/testDS 数据源: A:java:comp/env/jdbc/testDS B:jdbc/testDS   这两种写法,配置的方式也不尽相同,第一种方法应该算是一种利于程序移植或迁移的方法,它的实现与“映射”的概念相同,而B方法,则是一个硬引用. java:comp/env 是环境命名上下文(environment naming context(ENC)),是在EJB规范1.1以后引入的,引入这个是为了解决原来J