Entity 映射机制实现原理

本文是《轻量级 Java Web 框架架构设计》的系列博文。 java

为了开发一款轻量级的 Java Web 开发框架,我不惜放弃了我最深爱的 Hibernate。我很是感谢它这么多年来教会了我许多知识,让我不会再走弯路。既然没有弯路,那么什么才是捷径呢?那就是尽量的简单,经过惯例(Convention)而不是配置(Configuration)。说通来说就是彻底不须要任何的配置,就连注解都不须要! 程序员

下面这个 Entity,我想足以让你们明白。 sql

public class Product extends BaseEntity {
    private long productTypeId;
    private String productName;
    private String productCode;
    private int price;
    private String description;

    public long getProductTypeId() {
        return productTypeId;
    }

    public void setProductTypeId(long productTypeId) {
        this.productTypeId = productTypeId;
    }
...
}

不须要 @Entity、@Table、@Column 这样的注解,同时也放弃了 @OneToMany 这类具备争议的功能。仅仅继承了 BaseEntity。对于 BaseEntity 以及有关 Entity 的架构思路,请参考我另一篇博文《对 Entity 的初步构思》。 数据库

那么框架又是怎样加载 classpath 中全部的 Entity 呢?仍是用代码来讲明一切吧。 架构

public class EntityHelper {
    private static final Map<Class<?>, Map<String, String>> entityMap = new HashMap<Class<?>, Map<String, String>>();

    static {
        // 获取并遍历全部 Entity 类
        List<Class<?>> entityClassList = ClassHelper.getClassList(BaseEntity.class);
        for (Class<?> entityClass : entityClassList) {
            // 获取并遍历该 Entity 类中的全部方法(不包括父类中的方法)
            Field[] fields = entityClass.getDeclaredFields();
            if (ArrayUtil.isNotEmpty(fields)) {
                // 建立一个 Field Map(用于存放 Column 与 Field 的映射关系)
                Map<String, String> fieldMap = new HashMap<String, String>();
                for (Field field : fields) {
                    String fieldName = field.getName();
                    String columnName = StringUtil.toUnderline(fieldName); // 将驼峰风格替换为下划线风格
                    // 若 Field 与 Column 不一样,则须要进行映射
                    if (!fieldName.equals(columnName)) {
                        fieldMap.put(columnName, fieldName);
                    }
                }
                // 将 Entity 类与 Field Map 放入 Entity Map 中
                if (MapUtil.isNotEmpty(fieldMap)) {
                    entityMap.put(entityClass, fieldMap);
                }
            }
        }
    }

    public static Map<Class<?>, Map<String, String>> getEntityMap() {
        return entityMap;
    }
}

这个 EntityHelper 类会自动初始化 Entity 类与 Column/Field 的映射关系,并放入 entityMap中。此后,就能够经过 getEntityMap() 这个静态方法随时获取这些映射关系了,这个也就实现了所谓的 ORM(固然是轻量级的了)。 框架

以上这些都是框架为咱们提供的基础支持,还有一个成员不得不说,那就是 DBHelper 类,有了它以后,程序员就能够无需面对 JDBC 返回的 ResultSet 了,而是面对 JavaBean(或者说是 Entity),也就是说,自动将 ResultSet 转换为 JavaBean。在这里我得感谢 Apache Commons DbUtils 类库,它真的是太好了,很是轻量级,我只用了一次就爱上它了! ide

public class DBHelper {
    private static final BasicDataSource ds = new BasicDataSource();
    private static final QueryRunner runner = new QueryRunner(ds);

    static {
        ds.setDriverClassName(ConfigHelper.getProperty("jdbc.driver"));
        ds.setUrl(ConfigHelper.getProperty("jdbc.url"));
        ds.setUsername(ConfigHelper.getProperty("jdbc.username"));
        ds.setPassword(ConfigHelper.getProperty("jdbc.password"));
        ds.setMaxActive(Integer.parseInt(ConfigHelper.getProperty("jdbc.max.active")));
    }

    // 获取数据源
    public static DataSource getDataSource() {
        return ds;
    }

    // 执行查询语句(返回一个对象)
    public static <T> T queryBean(Class<T> cls, String sql, Object... params) {
        Map<String, String> map = EntityHelper.getEntityMap().get(cls);
        return DBUtil.queryBean(runner, cls, map, sql, params);
    }

    // 执行查询语句(返回多个对象)
    public static <T> List<T> queryBeanList(Class<T> cls, String sql, Object... params) {
        Map<String, String> map = EntityHelper.getEntityMap().get(cls);
        return DBUtil.queryBeanList(runner, cls, map, sql, params);
    }

    // 执行更新语句(包括 UPDATE、INSERT、DELETE)
    public static int update(String sql, Object... params) {
        return DBUtil.update(runner, sql, params);
    }
}

如今只是提供了最为典型的数据库操做,也就是 SELECT、UPDATE、INSERT、DELETE 了。未来或许还会有一些平常使用的方法,在这个 DBHelper 类里面进行扩展吧。 单元测试

细心的读者可能已经看到了,在 DBHelper 中还有一个 DBUtil,这个类又是什么呢?难道就是对 Apache Commons DbUtils 的封装类?对头! 测试

public class DBUtil {
    // 查询(返回 Array)
    public static Object[] queryArray(QueryRunner runner, String sql, Object... params) {
        Object[] result = null;
        try {
            result = runner.query(sql, new ArrayHandler(), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return result;
    }

    // 查询(返回 ArrayList)
    public static List<Object[]> queryArrayList(QueryRunner runner, String sql, Object... params) {
        List<Object[]> result = null;
        try {
            result = runner.query(sql, new ArrayListHandler(), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return result;
    }

    // 查询(返回 Map)
    public static Map<String, Object> queryMap(QueryRunner runner, String sql, Object... params) {
        Map<String, Object> result = null;
        try {
            result = runner.query(sql, new MapHandler(), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return result;
    }

    // 查询(返回 MapList)
    public static List<Map<String, Object>> queryMapList(QueryRunner runner, String sql, Object... params) {
        List<Map<String, Object>> result = null;
        try {
            result = runner.query(sql, new MapListHandler(), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return result;
    }

    // 查询(返回 Bean)
    public static <T> T queryBean(QueryRunner runner, Class<T> cls, Map<String, String> map, String sql, Object... params) {
        T result = null;
        try {
            if (MapUtil.isNotEmpty(map)) {
                result = runner.query(sql, new BeanHandler<T>(cls, new BasicRowProcessor(new BeanProcessor(map))), params);
            } else {
                result = runner.query(sql, new BeanHandler<T>(cls), params);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return result;
    }

    // 查询(返回 BeanList)
    public static <T> List<T> queryBeanList(QueryRunner runner, Class<T> cls, Map<String, String> map, String sql, Object... params) {
        List<T> result = null;
        try {
            if (MapUtil.isNotEmpty(map)) {
                result = runner.query(sql, new BeanListHandler<T>(cls, new BasicRowProcessor(new BeanProcessor(map))), params);
            } else {
                result = runner.query(sql, new BeanListHandler<T>(cls), params);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return result;
    }

    // 查询指定列名的值(单条数据)
    public static <T> T queryColumn(QueryRunner runner, String column, String sql, Object... params) {
        T result = null;
        try {
            result = runner.query(sql, new ScalarHandler<T>(column), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return result;
    }

    // 查询指定列名的值(多条数据)
    public static <T> List<T> queryColumnList(QueryRunner runner, String column, String sql, Object... params) {
        List<T> result = null;
        try {
            result = runner.query(sql, new ColumnListHandler<T>(column), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return result;
    }

    // 查询指定列名对应的记录映射
    public static <T> Map<T, Map<String, Object>> queryKeyMap(QueryRunner runner, String column, String sql, Object... params) {
        Map<T, Map<String, Object>> result = null;
        try {
            result = runner.query(sql, new KeyedHandler<T>(column), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return result;
    }

    // 更新(包括 UPDATE、INSERT、DELETE,返回受影响的行数)
    public static int update(QueryRunner runner, String sql, Object... params) {
        int result = 0;
        try {
            result = runner.update(sql, params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return result;
    }
}

在 DBUtil 中,已经对 Apache Commons DbUtils 作了大量的封装,其彷佛这个类对于程序员们来说,不到万不得已,尽可能不要用,最好仍是用 DBHelper 吧。若是 DBHelper 中的方法不够用了,不妨给你的架构师提一个需求,让他去扩展 DBHelper。  this

那么 Service 层又是如何调用 DBHelper 的呢? 来看看这个 ProductService 吧。

public interface ProductService {
    Product getProduct(long productId);
}
以上只是一个接口而已,实现类应该如何编写呢?别着急,往下看。
public class ProductServiceImpl extends BaseService implements ProductService {
    @Override
    public Product getProduct(long productId) {
        String sql = SQLHelper.getSQL("select.product.id");
        return DBHelper.queryBean(Product.class, sql, productId);
    }
}

这里先从 SQLHelper 中拿到 SQL 语句(带有占位符“?”的),而后调用 DBHelper 的 queryBean() 方法,传入须要转换的 Java 类(Product.class)、SQL 语句(sql)、填充 SQL语句占位符的参数(productId)。返回值就是 Product 对象了。是否是很简单呢? 不对!这里为何没有数据库链接呢? 这个问题留做读者本身思考吧。

有必要看看 SQLHelper 类是如何实现的,虽然它很简单,只是读取 properties 文件,并经过 key 获取 value(也便是 SQL)而已。

public class SQLHelper {
    private static final Properties sqlProperties = FileUtil.loadPropertiesFile("sql.properties");

    public static String getSQL(String key) {
        String value = "";
        if (sqlProperties.containsKey(key)) {
            value = sqlProperties.getProperty(key);
        } else {
            System.err.println("Can not get property [" + key + "] in sql.properties file.");
        }
        return value;
    }
}

以上代码真的很简单吧?那么,sql.properties 的内容还有悬念吗?

select.product.id = select * from product where id = ?

程序员们要作的就是写这个 sql.properties,还有 XxxService,以及 XxxServiceImpl 了。

万事俱备,只欠东风。最后来一个单元测试吧!

public class ProductServiceTest {
    private ProductService productService = new ProductServiceImpl();

    @Test
    public void loginTest() {
        long productId = 1;
        Product product = productService.getProduct(productId);
        Assert.assertNotNull(product);
    }
}

这里没有对 productService 进行依赖注入,是否须要依赖注入呢?应该如何进行依赖注入呢?又是如何实现依赖注入的呢?这些问题留做下一篇博文与你们分享。

来一张高清无码类图吧。

灰色虚线圈出来得三个类是程序员要作的。灰色是 Base 类,黄色是 Helper 类,蓝色是 Util 类,还有一个 sql.properties 也是程序员要写的。

请你们不要吝啬,随便点评!


补充(2013-09-04)

很是感谢网友们的评价!对与 Entity 的结构有必要稍做修改,细节以下:

若是列名是 Java 关键字(例如:列名为 class),那么在 Entity 中必定不能定义一个名称为 class 的字段。因此,我仍是采用了 @Column 注解,将列名写在注解中。

public class Product extends BaseEntity {
    private long productTypeId;

    private String productName;

    private String productCode;

    private int price;

    private String description;

    @Column("class")
    private int clazz;

    public long getProductTypeId() {
        return productTypeId;
    }

    public void setProductTypeId(long productTypeId) {
        this.productTypeId = productTypeId;
    }
...
}
在 Product 实体的 clazz 字段上定义了一个 @Column("class") 注解,这就觉得着将列名 class 映射为字段名 clazz。并且,若是使用了 @Column 注解,那么就不会采用默认的映射规则(用“下划线风格”的列名映射“驼峰风格”的字段名)。那么这又是如何实现的呢?对 EntityHelper 稍做修改便可实现。
...
for (Field field : fields) {
    String fieldName = field.getName();
    String columnName;
    // 若该字段上存在 @Column 注解,则优先获取注解中的列名
    if (field.isAnnotationPresent(Column.class)) {
        columnName = field.getAnnotation(Column.class).value();
    } else {
        columnName = StringUtil.toUnderline(fieldName); // 将驼峰风格替换为下划线风格
    }
    // 若字段名与列名不一样,则须要进行映射
    if (!fieldName.equals(columnName)) {
        fieldMap.put(columnName, fieldName);
    }
}
...
经过一个 if...else... 就轻易地解决了此问题。

那么确定会有人要问:若是数据库的表名是 Java 关键字,那么必定会与 Entity 的名称相冲突,那是否应该在 Entity 上标注一个 @Table("表名") 注解呢?

我我的认为是不须要这样作的,为何呢?由于 SQL 语句都是程序员本身编写的,并统一写在 sql.properties 文件中,在 SQL 语句上用的就是表名,咱们须要映射的是列名,仅此而已。

至于 Entity 中有些字段是否必定要与列名对应?这个问题,我还要细想一下,也请广大网友给出建议。

相关文章
相关标签/搜索