Entity 映射机制实现原理

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

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

下面这个 Entity,我想足以让大家明白。

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 类库,它真的是太好了,非常轻量级,我只用了一次就爱上它了!

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。 

那么 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 中有些字段是否一定要与列名对应?这个问题,我还要细想一下,也请广大网友给出建议。

转载于:https://my.oschina.net/huangyong/blog/158916

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值