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方法,该类是线程安全的,并且适合采用单例模式。至于为什么不直接用静态方法,在网上看到的是:静态方法一般都是业务无关的,而DAO方法都是业务相关的,并且创建对象可以方便框架(Spring)进行依赖注入,采用多态等。

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

后续修改↓

1、简化dao层方法命名

2、更改LocalConnectionFactory.java,依然采用Class.forName

package org.sample.db;

import org.sample.exception.DaoException;

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<>(); static {
try {
Class.forName("com.mysql.jdbc.Driver");
// 虽然说JDBC4之后已经不再需要Class.forName,但是能否
// 自动注册和环境、版本的相关性很大,所以安全起见还是加上这句比较好。
} catch (ClassNotFoundException e) {
// TODO 日志
throw new DaoException("could not register JDBC driver", e);
}
} 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() {
// TODO 应该先关掉再remove?
LocalConnectionHolder.remove();
}
}

LocalConnectionFactory.java

3、补一张结构草图(WRONG!!!)

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

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

    官方教程(包括 javase的基础部分):JDBC Basics 重新梳理.学习一下“Java连接数据库”相关的内容. 因为最开始没有认真学多线程和JDBC,一直在自己写的多线程程序中维持下面的错误写 ...

  2. java连接数据库(jdbc)的标准规范

    java连接数据库的标准规范 JDBC全称:java database connectivity ,是sun公司提供的Java连接数据库的标准规范. localhost和127.0.0.1 都是表示当 ...

  3. 学数据库你竟然不用用JAVA写代码,可惜你遇到了我! JAVA连接数据库(JDBC)的安装使用教程

    Step 1 你得有Eclipse 没有出门右拐,我教不了你. Step 2 你得有Mysql MySQL的详细安装过程,我在另一篇博客中给出.戳我 Step 3 安装JDBC 可以去官网下,如果用的 ...

  4. JAVA连接数据库 #03# HikariCP

    索引 为什么用数据库连接池? HikariCP快速入门 依赖 简单的草稿程序 设置连接池参数(只列举常用的) MySQL配置 修改Java连接数据库#02#中的代码 测试 为什么用数据库连接池? 为什 ...

  5. 从零开始学JAVA(04)-连接数据库MSSQL(JDBC准备篇)

    在JAVA中可以使用JDBC连接数据库,不管是哪种数据库,首先必须下载驱动,包括Windows的MSSQL. 1.下载MSSQL的JDBC驱动,可以通过百度“Microsoft JDBC Driver ...

  6. 完整java开发中JDBC连接数据库代码和步骤[申明:来源于网络]

    完整java开发中JDBC连接数据库代码和步骤[申明:来源于网络] 地址:http://blog.csdn.net/qq_35101189/article/details/53729720?ref=m ...

  7. java连接数据库(jdbc)调用配置文件

    各种语言都有自己所支持的配置文件,后缀名“.properties”结尾的就是其中之一. 在java连接数据库时,采取读取配置文件的方式,来获取数据库连接. 新建jdbc.properties文件,内容 ...

  8. Java数据库连接技术——JDBC

    大家好,今天我们学习了Java如何连接数据库.之前学过.net语言的数据库操作,感觉就是一通百通,大同小异. JDBC是Java数据库连接技术的简称,提供连接各种常用数据库的能力. JDBC API ...

  9. Java连接数据库的辣几句话

    Java连接数据库的辣几句话 1.java连接Oracle数据库 使用以下代码三个步骤: 1.下载ojdbc.jar包并导入项目中.附下载地址:http://download.csdn.net/det ...

随机推荐

  1. LightGBM调参总结

    1. 参数速查 使用num_leaves,因为LightGBM使用的是leaf-wise的算法,因此在调节树的复杂程度时,使用的是num_leaves而不是max_depth. 大致换算关系:num_ ...

  2. iOS UI基础-13.0 数据存储

    应用沙盒 每个iOS应用都有自己的应用沙盒(应用沙盒就是文件系统目录),与其他文件系统隔离.应用必须待在自己的沙盒里,其他应用不能访问该沙盒 应用沙盒的文件系统目录,如下图所示(假设应用的名称叫Lay ...

  3. cocos2d JS touch屏幕点击事件监听 cc.EventListener.TOUCH

    var self = this; this.touchListener = cc.EventListener.create({ event: cc.EventListener.TOUCH_ONE_BY ...

  4. 使用mysqlbinlog从二进制日志文件中查询mysql执行过的sql语句 (原)

    前提MySQL开启了binlog日志操作1. 查看MySQL是否开启binlog(进mysql操作) mysql> show variables like 'log_bin%';       2 ...

  5. react结合redux开发

    先加上我码云的地址:https://gitee.com/ldlx/react_and_rudex

  6. InstallShield2015制作安装包----------卸载后删除安装目录和文件

    卸载程序后,一般是需要将安装目录清除干净.但是,如果程序运行中有文件生成,这时InstallShield自带的卸载程序,不会卸载这些运行时生成的文件. 卸载不干净,可能会对下次程序的安装,和安装后的运 ...

  7. HDU 3117 Fibonacci Numbers(矩阵)

    Fibonacci Numbers [题目链接]Fibonacci Numbers [题目类型]矩阵 &题解: 后4位是矩阵快速幂求,前4位是用log加Fibonacci通项公式求,详见上一篇 ...

  8. hibernate添加数据入门小案例

    1.建立一个java项目,在目录下新建一个lib文件夹引入hibernate架包如图所示: 2. 新建com.LHB.domain包,在包中分别创建一个Employee.java和Employee.h ...

  9. oauth2.0+app方式 webgis 授权

    .认证方式有三种 Oauth2.0, Token-based http/windows 二.用户登录与应用登录区别 两者区别在于:当用户登录时,服务器端平台是否直接识别登录信息和验证登录信息. 应用登 ...

  10. 域名重新绑定ip时访问地址NotFount404

    情形描述:部署在A服务器IIS上的asp.net程序,搬迁到B服务器上,重新绑定域名和ip后.再访问网址时有些电脑能正常访问,而有些电脑报404 not found错误. 经分析发现是个人电脑网络设置 ...