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