3

我在静态方法中使用通用类型时遇到了一些麻烦。

欢迎所有对源代码的评论,尤其是那些显着改进代码的评论。我目前也不打算使用任何外部框架,除了 JDBC,以保持它仍然简单,请不要过分强调这一点。

我对不使用外部框架的看法也得到了以下事实的支持:我将在数据库上使用的操作非常少:

  • 插入数据
  • 更新数据
  • 检索所有字段。(只需输入不同的 SQL 查询,您就可以选择要检索的字段

我不打算制作一个完整的框架,所以我知道它不会支持一切。检索所有字段的速度并不是一个真正的问题,因为这几乎只在服务器启动时完成,如果在任何其他时间使用,它将在后台任务中完成,我并不关心它何时完成.

实体.java:

abstract public class Entity<KeyType, DataType> {
    protected KeyType key;
    protected List<Object> data;

    public Entity() {
        data = new ArrayList<>();
    }

    //abstract public static Map<KeyType, DataType> getAll();

    protected List<Object> createData(final DataAction dataAction) {
        List<Object> list = new ArrayList<>();
        if (dataAction == DataAction.INSERT) {
            list.add(key);
        }

        list.addAll(data);

        if (dataAction == DataAction.UPDATE) {
            list.add(key);
        }
        return list;
    }

    abstract public void insert();

    abstract public void update();

    protected static <KeyType, DataType> Map<KeyType, DataType> getData(final Class<DataType> dataTypeClass, final String query) {
        Map<KeyType, DataType> map = new HashMap<>();
        try {
            PreparedStatement preparedStatement = DatabaseConnection.getConnection().prepareStatement(query);
            ResultSet resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                KeyType key = (KeyType)resultSet.getObject(1);
                int index = 2;
                List<Object> dataList = new ArrayList<>();
                while (resultSet.getObject(index) != null) {
                    dataList.add(resultSet.getObject(index));
                    index++;
                }
                DataType dataObject = null;
                try {
                    dataObject = dataTypeClass.getConstructor(List.class).newInstance(dataList);
                } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException ex) {
                    Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, null, ex);
                }
                map.put(key, dataObject);
            }
        } catch (SQLException ex) {
            Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, null, ex);
        }        
        return map;
    }

    protected void executeQuery(final String query, final List<Object> data) {
        try {
            PreparedStatement preparedStatement = DatabaseConnection.getConnection().prepareStatement(query);
            int dataIndex = 0;
            for (Object dataObject : data) {
                preparedStatement.setObject(dataIndex, dataObject);
                dataIndex++;
            }
            preparedStatement.execute();
            preparedStatement.close();
        } catch (SQLException ex) {
            Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

一个具体的实现,Account.java:

public class Account extends Entity<String, Account> {
    private final static String SELECT_ALL_QUERY = "SELECT * FROM accounts";
    private final static String INSERT_QUERY = "INSERT INTO accounts (username, password) VALUES(?, ?)";
    private final static String UPDATE_QUERY = "UPDATE accounts SET password=? WHERE username=?";

    private String username;
    private String password;

    public Account(final String username, final String password) {
        this.username = username;
        this.password = password;

        key = username;
        data.add(password);
    }

    public Account(final List<Object> data) {
        this((String)data.get(0), (String)data.get(1));
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(final String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(final String password) {
        this.password = password;
    }

    public static Map<String, Account> selectAll() {
        return getData(Account.class, SELECT_ALL_QUERY);
    }

    @Override
    public void insert() {
        executeQuery(INSERT_QUERY, createData(DataAction.INSERT));
    }

    @Override
    public void update() {
        executeQuery(UPDATE_QUERY, createData(DataAction.UPDATE));
    }
}

我对具体的实现总体上很满意,似乎我已经设法将它降低到最低限度,除了public Account(final List<Object> data)看起来不太好,但我可以忍受它。

但是,正如猜测的那样,getData()fromEntity肯定不好,如果可能的话,我想改进它。

我想使用的是类似的东西DataType dataObject = new DataType(dataList),但似乎无法实例化通用类型参数。

那么有什么方法可以在我当前的视图中优化我当前的代码吗?是否可以进一步解耦具体类和抽象类?

编辑:

添加了一个相关问题(我认为我不应该为这件事提出一个全新的问题,对吧?):

有没有办法将静态字符串(SQL 查询)和 Account 类中的insert()update()移出到 Entity 类中?

4

2 回答 2

2

为了避免在你的getData方法中使用反射,你应该接受一个工厂,它给定了一个ResultSet创建特定类型的实例。您的selectAll方法将类似于:

public static Map<String, Account> selectAll() 
{
  return getData(
    new EntityFactory<Account>()
    {
      public Account newInstance(ResultSet resultSet) throws SQLException
      {
        return new Account(resultSet.getString(0), resultSet.getString(1));
      }
    },
    SELECT_ALL_QUERY
  );
}

然后该getData方法以如下方式结束:

protected static <K, T extends Entity<K>> Map<K, T> getData(EntityFactory<T> entityFactory, String query) 
{
  Connection connection = null;
  PreparedStatement preparedStatement = null;      
  ResultSet resultSet = null;

  try 
  {
    connection = dataSource.getConnection();

    preparedStatement = connection.prepareStatement(query);

    resultSet = preparedStatement.executeQuery();

    Map<K, T> entities = new HashMap<>();

    while (resultSet.next()) 
    {
      Entity<K> entity = entityFactory.newInstance(resultSet);
      entities.put(entity.getKey(), entity);
    }

    return entities;
  }
  finally 
  {
    closeQuietly(resultSet);
    closeQuietly(prepareStatement);
    closeQuietly(connection);
  }
}

并假设Entity看起来像:

public interface Entity<K>
{
  public K getKey();
}

这允许您删除反射并将理解数据库结构的代码保留在一个地方。在进行插入和更新时,您还应该使用类似的模板模式从域对象映射到准备好的语句。

现在您已经要求对代码进行总体评论。

首先,这样的代码违反了单一职责原则关注点分离。域类应该是域类并且不包含持久性逻辑。查看数据访问对象之类的模式,了解应该如何完成。

其次,虽然我完全赞成保持简单,但Hibernate很久以前就解决了这个问题,并且JPA对其进行了标准化——你需要一个很好的理由不使用这些 API 中的一个或两个。

最后,您对数据库资源的使用——如果您要直接使用 JDBC,则必须正确清理。数据库连接是昂贵的资源,应该这样处理,任何 JDBC 调用的基本模板应该是:

Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;

try 
{
  connection = //get connection from pool or single instance.

  preparedStatement = connection.prepareStatement("SELECT * FROM table WHERE column = ?");
  preparedStatement.setString(1, "some string");

  resultSet = preparedStatement.executeQuery();

  while (resultSet.next())
  {
    //logic goes here.
  }
}
catch (SQLException e)
{
  //Handle exceptions.
}
finally 
{
  closeQuietly(resultSet);
  closeQuietly(prepareStatement);
  closeQuietly(connection);
}

closeQuietly方法必须重载,但应采用一般形式:

try 
{
  if (resultSet != null)
  {
    resultSet.close();
  }
}
catch (SQLException e)
{  
  //Log exceptions but don't re-throw.
} 
于 2013-05-14T16:32:01.817 回答
0

好吧,正如 Darwind 和 Nick Holt 告诉您的,在正常情况下,您应该使用JPA,它是 Java 对象关系映射的标准规范。您可以使用HibernateEclipseLink或任何其他框架。他们的设计是可以管理连接、事务。此外,使用标准而不是外来框架意味着您可以更轻松地为社区获得帮助。另一种选择是使用 Spring JDBC,它非常轻量级并且方便了很多事情。

无论如何,我想你这样做是为了学习目的,所以让我们更进一步。

首先,我认为您应该将负责或检索数据的类(称为管理器或数据访问对象-DAO-)与表示数据本身的实体分开。

对我来说,使用类来获取所有数据本身并不是问题。问题是密钥的位置是硬编码的。这不应该直接确定为泛型(我的意思是所有实体实现都相同)。当第一个字段不是键(您确定select * from...总是将键返回第一个位置吗?)或使用复合键时,这会使查询受到错误的影响。我认为更好的解决方案是创建一个Mapper接口并为每个实体实现它。

public interface RecordMapper<KeyType, DataType extends Entity> {
    public void appendToMap(ResultSet resultSet, Map<KeyType, DataType>) throws SQLException;
}

映射器的实现应该负责实例化您的实体,从结果集中检索键,填充您的实体并将其放入您期望的映射中。

public class AccountMapper implement RecordMapper<String, Account>{
    public void appendToMap(ResultSet resultSet, Map<String, Account> accounts) throws SQLException {
        String user= resultSet.getString("userName");
        String pwd= resultSet.getString("passWord");
        Account account = new Account(user, pwd);
        accounts.put(user, account);
    }
}

正如我告诉你的,你应该在 DAO 中移动你的数据访问方法:public class DAO{

    public <KeyType, DataType> Map<KeyType, DataType> getData(final RecordMapper<KeyType, DataType> mapper, final String query) {
        Map<KeyType, DataType> map = new HashMap<>();
        try {
            PreparedStatement preparedStatement = DatabaseConnection.getConnection().prepareStatement(query);
            ResultSet resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                mapper.appendToMap(resultSet, map);
            }
        } catch (SQLException ex) {
            Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            if(resultSet != null){
                try{resultSet.close();} catch (Exception e){}
            }
            if(preparedStatement!= null){
                try{preparedStatement.close();} catch (Exception e){}
            }
        }
        return map;
    }


    public void executeQuery(final String query, final List<Object> data) {
        try {
            PreparedStatement preparedStatement = DatabaseConnection.getConnection().prepareStatement(query);
            int dataIndex = 0;
            for (Object dataObject : data) {
                preparedStatement.setObject(dataIndex, dataObject);
                dataIndex++;
            }
            preparedStatement.execute();
        } catch (SQLException ex) {
            Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            if(resultSet != null){
                try{resultSet.close();} catch (Exception e){}
            }
            if(preparedStatement!= null){
                try{preparedStatement.close();} catch (Exception e){}
            }
        }
    }
}

为了回答您的第二个问题,我认为将您的请求字符串放在抽象父级而不是肯定不是一个好主意。每次创建新实体时,都必须在父实体中创建一个新查询。奇怪......除非我没有正确理解你的问题。

我个人认为查询应该是动态构建的,你应该使用反射注释,但答案应该有点长。再一次,您可以查看 JPA 以了解创建实体的外观。顺便说一句,如果实体不必扩展父实体类,那应该会更好。

于 2013-05-14T17:46:23.637 回答