我正在运行一个Spring MVC应用程序,由我使用JDBC访问的MySQL数据库提供支持。我一直在使用相同的代码,并且从未真正了解我是否正确使用它(正确使用连接池等)。
我知道那里有 JDBCTemplate ,我已经考虑过使用它,但如果唯一的好处是我只是不必编写样板代码,那么我就是不太相信我应该使用它。实际上,我更喜欢我的代码对JDBCTemplate代码的可读性。
以下是我的DAO中的代码,出于某种原因,我觉得我没有正确使用ConnectionPooling。
public Agent get(Integer id){
ConnectionPool pool = new ConnectionPool();
Connection connection = pool.getConnection();
PreparedStatement ps = null;
try{
String query = "SELECT * FROM agent where id= ?";
ps = connection.prepareStatement(query);
ps.setInt(1,id);
ResultSet rs = ps.executeQuery();
Agent agent = null;
if(rs.next()){
agent = new Agent();
agent.setFirstName(rs.getString(1));
agent.setLastName(rs.getString(2));
agent.setStreet(rs.getString(3));
agent.setCity(rs.getString(4));
agent.setZip(rs.getString(5));
agent.setState(rs.getString(6));
agent.setUsername(rs.getString(7));
agent.setPassword(rs.getString(8));
agent.setId(rs.getInt(9));
agent.setEmail(rs.getString(10));
}
return agent;
}
catch(SQLException e)
{
e.printStackTrace();
return null;
}
finally{
ConnectionUtility utility = new ConnectionUtility();
utility.closePreparedStatement(ps);
pool.freeConnection(connection);
}
}
上面的代码是我最担心的最不正确的,但我有一些实用程序类,也可能导致不良做法/不正确的代码。
以下是我的 ConnectionUtility 类。
public class ConnectionUtility{
public static void closeStatement(Statement s){
try{
if(s != null){
s.close();
}
}
catch(SQLException e){
e.printStackTrace();
}
}
public void closePreparedStatement(Statement ps){
try{
if(ps != null){
ps.close();
}
}
catch(SQLException e){
e.printStackTrace();
}
}
public static void closeResultSet(ResultSet rs){
try{
if(rs != null){
rs.close();
}
}
catch(SQLException e){
}
}
}
这是我的 ConnectionPool 类,
public class ConnectionPool {
private static ConnectionPool pool = null;
public ConnectionPool(){
}
public static ConnectionPool getInstance(){
if(pool == null){
pool = new ConnectionPool();
}
return pool;
}
@SuppressWarnings("static-access")
public Connection getConnection(){
try{
return ConnectionFactory.getInstance().getConnection();
}
catch(SQLException e){
e.printStackTrace();
return null;
}
}
public void freeConnection(Connection c){
try{
c.close();
}
catch(SQLException e){
e.printStackTrace();
}
}
}
同样,我觉得我确实错误地使用了所有这些类,即使一切正常,但在生产中没有进行任何测试。
我更喜欢坚持使用JDBC,所以请不要建议切换到另一个。
答案 0 :(得分:6)
Spring的核心原则之一是dependency injection。 Spring基于这样的信念:将类所使用的组件注入该类会导致代码比使用类/代码更容易阅读,更易于测试和更易于维护(例如您的{{1 }}方法)负责找到自己的依赖项。
作为更具体术语的示例:您的get()
方法至少取决于其他两个类:1)“连接池”和2)连接本身。 get()
方法非常了解获取这些实例所需的。
作为此处编码风格的替代方法,使用DI方法,拥有get()
方法的类会将get()
(或Connection
)注入其中(通过setter或constructor injection。。
现在,为什么这个简单的改变使代码更容易和更好?
因为Datasource
方法不再需要关注不是核心责任的细节。 get()
方法的核心职责是了解如何get()
获得Agent
。为什么这个方法还需要知道1)从哪里获取连接和2)你想要连接池?
当您想要更改此连接逻辑时会发生什么?您需要触摸应用程序中每个和每个数据访问方法的代码。这将是一个远远超过它需要的变化。
这是依赖注入的强大功能:它允许您更改细节(例如JDBC连接的来源),而不必更改使用这些细节的代码。
至于你的实际连接池代码,似乎你误解了两个概念:
1)您的Integer id
声明要成为单身人士,但是您公开了一个公共构造函数,允许协作者完全默认使用单个ConnectionPool
实例的目的。
2)您的连接池实际上不是连接池!连接池的想法是打开与数据库的N个连接,然后将这N个连接中的每个连接分发给需要按需连接的代码。这里的核心思想是,您可以回收连接并避免为每个请求打开新连接的昂贵成本。在连接池中,当使用连接的代码完成它的连接时,物理连接实际上不会终止 - 而是简单地将连接句柄返回到池以供另一个请求/线程/方法再次使用。
最重要的是,在使用连接池的应用程序中,负责数据访问的代码通常甚至不知道它的连接正在汇集 - 相反,DAO只是引用了{{ 1}}接口和DAO不知道当它向ConnectionPool
询问连接时发生了什么,或者在释放连接时会发生什么。因此,您可以从负责高阶逻辑的代码中抽象出“我如何连接”的细节,例如“如何从此整数中获取代理?”。这种抽象允许您在不重写所有其他层的情况下更改应用程序的一个层 - 您已经拆分了层,每个层只关注它实际负责的内容。
我强烈建议你不仅要更多地了解连接池,还需要Dependency Injection。为什么在没有DI组件的情况下你会使用Spring?至于连接池,为什么要花时间重新发明轮子,而不是使用像commons-dbcp或c3p0那样已经存在且流行的库?而不是重新发明轮子,使用现有的库(不太可能有比家庭酿造的解决方案更多的错误),并专注于构建您的实际应用程序。
答案 1 :(得分:3)
您的代码存在两个问题:
1)您正在为get()
的每次调用创建一个新的ConnectionPool对象。您的ConnectionPool类设计为单例,因此通过将构造函数设为私有并将客户端代码更改为ConnectionPool pool = ConnectionPool.getInstance()
来强制执行它。
2)与ConnectionUtility基本相同的问题。您有静态方法,但是您以非静态方式使用它们。替换
ConnectionUtility utility = new ConnectionUtility();
utility.closePreparedStatement(ps);
与
ConnectionUtility.closePreparedStatement(ps);
并使ConnectionUtility构造函数为private,类为final。
单身人士和实用程序类可能很难做到正确,所以我建议你使用像Findbugs这样的静态分析工具来解决这些问题。当你有一个看起来应该是单例或实用程序类但没有以这种方式使用的类时,它会警告你。