我正在开展一个项目,我正在做很多查询,时间是一个考虑因素,所以我想尝试实现JDBC多线程。我不确定这样做的正确方法是什么。
这是我的第一份实施草案:
Spring数据源Bean:
private DataSource ds;
@Resource(name="jdbc")
public void setDataSource(DataSource ds) {
this.ds = ds;
}
初始化方法:
public void checkUsersMulti(List<User> users) throws Exception {
if(users!= null || users.size() != 0) {
ExecutorService executorService = Executors.newFixedThreadPool(20);
Queue<User> queue = new ConcurrentLinkedQueue<>();
queue.addAll(users);
for (Useruser: users) {
executorService.submit(new ProcessUser(queue));
}
executorService.shutdown();
try {
executorService.awaitTermination(1, TimeUnit.HOURS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
可运行的课程:
class ProcessUser implements Runnable {
private final Queue<User> queue;
public ProcessUser(Queue<User> queue) {
this.queue = queue;
}
public void run() {
try {
Connection conn = ds.getConnection();
User user = null;
while((user = queue.poll()) != null) {
userDao.getUser(user , conn));
}
DbUtils.closeQuietly(conn);
} catch (Exception e) {
e.printStackTrace();
}
}
}
用户DAO方法:
public User retrieveUser(User user, Connection conn) {
PreparedStatement st = null;
ResultSet rs = null;
try {
String sql = "SELECT firstname, lastname FROM users WHERE id= ?;
st = conn.prepareStatement(sql);
st.setString(1, user.getId());
rs = st.executeQuery();
while(rs.next()) {
user.setFirstName(rs.getString("firstname"));
user.setLastName(rs.getString("lastname"));
}
} catch (Exception e) {
return null;
}
finally {
DbUtils.closeQuietly(rs);
DbUtils.closeQuietly(st);
}
return user;
}
更新:Tomcat JNDI连接池设置:
<Resource
name="jdbc"
auth="Container"
type="javax.sql.DataSource"
maxTotal ="25"
maxIdle="30"
maxWaitMillis ="10000"
driverClassName="***Vendor Driver***"
url="***Valid URL"
username="***Valid Username***"
password="***Valid Password***"
/>
但是,我偶尔会收到此错误:
java.sql.SQLException: Cannot get a connection, pool error Timeout waiting for idle object
但是该过程仍然按预期完成 - 它可能在我的JNDI设置中。我更担心的是,这是实现这一目标的“正确”方法。我认为最好保留连接对象,这样就不需要重新初始化了。
答案 0 :(得分:0)
这段代码有几个问题,最重要的是,我不确定这是什么:
public void checkUsersMulti(List<User> users) throws Exception {
if(users!= null || users.size() != 0) {
ExecutorService executorService = Executors.newFixedThreadPool(20);
Queue<User> queue = new ConcurrentLinkedQueue<>();
queue.addAll(users);
for (Useruser: users) {
executorService.submit(new ProcessUser(queue));
}
executorService.shutdown();
try {
executorService.awaitTermination(1, TimeUnit.HOURS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
应该这样做。如果我理解正确,你创建一个包含其中所有用户的队列,然后将其提交给执行者多次,就像有用户一样。这意味着:
您在处理的用户数量方面受到限制,因为队列无法无限增长(这可能是@sabit khan所担心的)
你必须为每个用户运行至少ONCE的runnable,尽管除非一些runnables因为错误而退出,前20个将继续运行直到队列为空,然后剩下的将被调用,参见队列为空,然后立即退出。
您应该限制启动的并行进程数。你有一个20的线程池,所以如果你正确关闭你的连接,应该永远不会有超过20个并发连接。你没有提到任何其他例外,但是这个:
try {
Connection conn = ds.getConnection();
User user = null;
while((user = queue.poll()) != null) {
userDao.getUser(user , conn));
}
DbUtils.closeQuietly(conn);
} catch (Exception e) {
e.printStackTrace();
}
应该是:
Connection conn = null;
try {
conn = ds.getConnection();
User user = null;
while((user = queue.poll()) != null) {
userDao.getUser(user , conn));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtils.closeQuietly(conn);
}
只是为了确保在任何情况下关闭连接。
另外,由于您显然正在使用底层连接池,因此您可能希望每次尝试获取并返回连接。
关于你的实际问题:你这样做的方式意味着前20个runnables试图获得连接,然后持有它们很长一段时间。如果您的池配置为使用提供的连接少于那个(假设为15),那么这很容易发生,因为前15个成功获得连接,然后接下来的5个工作人员将连续运行runnable,try,fail,exit 。或者您在池中使用了20个连接,您可能正在泄漏连接,因为您没有如上所述正确关闭它们。