有的时候我们数据库设计可能不会完全和代码语言一致,比如我们会在数据库的每一张表前面加上一个特定的前缀用于区分,在BeetlSQL中将代码Pojo的名称和数据库Table名称对应起来是使用NameConversion
来转换的,BeetlSQL内置了DefaultNameConversion
、UnderlinedNameConversion
和JPANameConversion
等转换器基本上可以满足绝大部分的要求的,今天就来给大家演示一下如何自定义NameConversion
。
###前言
要了解本文的内容,首先你需要先了解Beetl和BeetlSQL这两个项目,它们是来自于国人@闲·大赋呕心沥血精心打造的Java模板引擎和Java数据库全功能Dao,而且目前官方已经提供了Beetl和BeetlSQL与市面上各种主流MVC框架的整合方案
我不敢说Beetl比JSP、FreeMarker、Velocity等大家熟知的一些模板引擎怎么怎么的好,但是我想说以我个人的使用经历来做对比,Beetl绝对不会比它们差,BeetlSQL更是不会比Hibernate和MyBatis等差
如果有兴趣想了解一下Beetl和BeetlSQL,可以访问Beetl官方论坛查阅详情。
何不给Beetl一个机会来证明Beetl自己,更是给你自己一个学习工作尝试的机会,说不定你就和我一样一不留神就爱上了它。
###正文
本文讨论的是在使用BeetlSQL自动生成Pojo对象时,通过自定义转换器去除映射表名前缀的问题。
先看一组开发遇到的情况:
假定当前有个项目beetl在数据库设计的时候,所有的表名都有一个项目前缀bt_
,即所有的表都以bt_
开头,如bt_user
、bt_role
、bt_user_role
,看到这样的表名设计我们可能第一想到的是使用UnderlinedNameConversion
来进行转化,于是乎:
bt_user-> BtUser
bt_role -> BtRole
bt_user_role -> BtUserRole
相信很多人肯定和我一样是不能接受这样的命名的,所以我们需要自己定义一个NameConversion
来进行转换,去除掉Table
生成Pojo
时自动加上的Bt
前缀
NameConversion
中核心的几个方法getPropertyName
、getColName
、getTableName
、getClassName
,根据方法名和参数很容易理解
方法名
方法解释
getPropertyName
根据数据库的列名通过一系列自定义方法转化后生成Pojo的属性名
getColName
根据Pojo的属性名称通过一系列自定义方法转化后生成数据库列名
getClassName
根据数据库的表名通过一系列自定义方法转化后生成Pojo类的类名字符串
getTableName
根据Pojo的类的类型通过一系列自定义方法转化后生成数据库表名
因此我们只需要根据我们自己的实际需求重新实现一个NameConversion
将表名转类名、类名转表名的方法重新实现以下即可。
具体做法:首先我们新建一个类继承自DefaultNameConversion
类,分别重写getClassName
和getTableName
两个方法,最后在我们的项目中启用自己写的Conversion
@Override
public String getTableName(Class<?> c) {
//遵循BeetlSQL规范,@Table拥有最高优先级
Table table = (Table)c.getAnnotation(Table.class);
if(table!=null){
return table.name();
}
//UserRole -> user_role
String tableName = StringKit.enCodeUnderlined(c.getSimpleName());
//user_role -> bt_user_role
return "bt_"+tableName;
}
@Override
public String getClassName(String tableName){
//假定所有表都是以bt_开头
//bt_user_role -> user_role
tableName = tableName.substring(tableName.indexOf("_")+1);
//user_role -> userRole
String clsName = StringKit.deCodeUnderlined(tableName);
//userRole -> UserRole
return StringKit.toUpperCaseFirstOne(clsName);
}
就这样轻松的完成的整个数据库的表名去前缀。
事与愿违!!! 往往我们可能还会遇到更特殊的情况,下面让我们再来看一组开发可能遇到的情况:
当前有一个项目,里面有一些用户的数据表(如User),后台管理的数据表(如Admin)和以前用户与后台公共的数据表(如City),然后在开发时为了做区分,分别给不同的功能模块的表加上了不同的前缀,最后我们的表名可能是:usr_user
、mgr_admin
和base_city
这样的形式,这时候我们再使用上面的方式统一去前缀再统一加前缀肯定是行不通的,毕竟前缀不一样你去掉前缀后是很难直接还原的。
最初我遇到这样的问题,第一想法是,让BeetlSQL反向生成Pojo的时候,去除前缀然后自动给所有的Pojo加上@Table
的注解,例如例子中的usr_user
、mgr_admin
和base_city
三张表 最后可能生成如下形式:
usr_user -> @Table(name="usr_user") User
mgr_admin -> @Table(name="mgr_admin") Admin
base_city -> @Table(name="base_city") City
理想是丰满的,现实是骨感的。在QQ上与作者沟通遇到这种情况时的解决方案时发现可能会出现这样的情况: 前台用户使用的数据表可能是 usr_user
,而后台管理员也属于用户呀,因此可能后台管理员的数据表为mgr_user
,最后这样会导致两个类冲突了,生成代码的时候可能会被覆盖一个类,是有问题的。好在机智如我~遇到这样的根据功能模块给不同的表加上不同的前缀,我们在Java程序开发时也经常会使用不同的包名来区分不同的模块,我可以将不同前缀的实体放到对应的包下面,还原表名的时候读取一下包名即可反向解析出表名,下面是我的具体实现:
@Override
public String getClassName(String tableName){
//为了安全起见,做了个判断,理论上项目数据库设计好了应该是无需判断直接截取所有前缀
//usr_user_role -> user_role
if(tableName.matches("^(usr_|base_|mgr_).*")){
tableName = tableName.substring(tableName.indexOf("_")+1);
}
//user_role -> UserRole
String clsName = StringKit.deCodeUnderlined(tableName);
return StringKit.toUpperCaseFirstOne(clsName);
}
@Override
public String getTableName(Class<?> c) {
Table table = (Table)c.getAnnotation(Table.class);
if(table!=null){
return table.name();
}
//获取Package 最后一层 xxx.pojo.usr -> Usr
String pkg = c.getPackage().getName();
pkg = pkg.substring(pkg.lastIndexOf(".")+1);
pkg = Character.toUpperCase(pkg.charAt(0))+pkg.substring(1);
//Usr+User -> UsrUser -> usr_user
return StringKit.enCodeUnderlined(pkg+c.getSimpleName());
}
然后在使用BeetlSQL反向生成Pojo的时候,使用Filter将数据表分门别类的生成到不同的包下面:
//记得初始化SQLManager时要使用自己写好的NameConvertion
SQLManager sql = initSQLManager();
GenConfig config = new GenConfig();
sql.genALL("xxxx.pojo.usr", config, new GenFilter(){
@Override
public boolean accept(String tableName) {
return tableName.startsWith("usr_");
}
});
sql.genALL("xxxx.pojo.mgr", config, new GenFilter(){
@Override
public boolean accept(String tableName) {
return tableName.startsWith("mgr_");
}
});
sql.genALL("xxxx.pojo.base", config, new GenFilter(){
@Override
public boolean accept(String tableName) {
return tableName.startsWith("base_");
}
});
最终会在项目中会生成以下Pojo
usr_user -> xxxx.pojo.usr.User.java
mgr_admin -> xxxx.pojo.mgr.Admin.java
base_city -> xxxx.pojo.base.City.java
即便是有类名冲突的,因为在不同的包下,所以也不会影响
就这样一次轻松而又愉快的BeetlSQL自定义去除Pojo和表前缀被解决啦~
###最后 之前所有项目的开发都是Jsp,几乎没使用过其他第三方模板库,因为一次误打误撞让我认识了Beetl,它的轻巧,它独特的语法都深深的吸引了我,因为Beetl又让我认识了BeetlSQL,抱着试一试的心态尝试过之后总有一种把当前项目(使用的Hibernate)推倒重来的冲动,虽然最后因为项目的工程量和工期原因,当前的这个项目最终还是使用了Hibernate,但是我可以确定的是,在以后的开发道路上,如果允许,如果没有能让我更心动的通用Dao,BeetlSQL将会是我不二的选择。
今天在这里分享这篇自定义NameConvertion的功能,其实并没有多少技术含量,更多的是想做一次简单的推广,让更多人知道Beetl,让更多人爱上Beetl~