ORM在我们平时项目里是必不可少的,也是最重要的系统架构之一,它提供对数据访问的底层实现,比较出名的有Java里的Hibernate、.Net里的NHibernate和Linq,这些都是很成熟的ORM框架,今天我要说的是我的ORM,这当然不能和前面说的那些ORM比,这里仅当自己造轮子学习。
使用Attribute元数据
用过Hibernate等ORM的童鞋都知道,可以使用XML文件来配置数据表和实体对象的关系,虽然说这样可以更灵活一些,但我是比较反感这些繁杂的配置的,讨厌一大堆的配置文件,所以我这里是先采用了元数据的方法,将这些配置直接嵌入在代码里,使用Attribute元数据来标识实体对象和属性。一个简单的Breast实体类如下:
[EntityFlag(TableName = "bl_breast")]
public class Breast: ModelBase
{
private User user;
public User User
{
get { return user; }
set { user = value; }
}
private Int32 breastId;
[ColumnFlag( PrimaryKey=true)]
public Int32 BreastId
{
get { return breastId; }
set { breastId = value; }
}
private Int32 userId;
[ColumnFlag]
public Int32 UserId
{
get { return userId; }
set { userId = value; }
}
private String breast;
[ColumnFlag(ColumnName = "breast")]
public String BreastContent
{
get { return breast; }
set { breast = value; }
}
private DateTime addDate;
[ColumnFlag]
public DateTime AddDate
{
get { return addDate; }
set { addDate = value; }
}
上面的EntityFlag和ColumnFlag都是Attribute元数据,其中的TableName和ColumnName、PrimaryKey分别为元数据属性,代表对应的表名和列名。获取的时候也很简单,使用GetCustomAttributes能返回对象的所有元数据,获取对象所有ColumnFlag标识方法如下:
public static List<ColumnFlag> GetPropertyAnnotations(Type t)
{
List<ColumnFlag> propertyAnnotations = new List<ColumnFlag>();
PropertyInfo[] properties = t.GetProperties();
foreach (PropertyInfo property in properties)
{
object[] attrs = property.GetCustomAttributes(true);
foreach (object attr in attrs)
{
if (attr is ColumnFlag)
{
ColumnFlag propertyAnnotation = (ColumnFlag)attr;
if (String.IsNullOrEmpty(propertyAnnotation.ColumnName)) {
propertyAnnotation.ColumnName = property.Name;
}
propertyAnnotation.TargetPropertyInfo = property;
propertyAnnotations.Add(propertyAnnotation);
}
}
}
return propertyAnnotations;
}
元数据在Java里叫注解,hibernate和springframework中有大量的应用,如Controller控制层的里URL路由地址配置:
@Controller
@RequestMapping("/member/account.do")
public class AccountController {
@Autowired
private MemberService memberService;
@RequestMapping(params = "action=editin")
public ModelAndView edit_in() {
HashMap<String, Object> model = new HashMap<String, Object>();
List<Loving> lovings = memberService.listAllLoving();
model.put("lovings", lovings);
List<Department> departments = memberService.listAllDepartment();
model.put("departments", departments);
return new ModelAndView("/member/edit", model);
}
}
这些配置都是ORM的核心,通过获取这些信息就可为生成SQL语句做准备了,而访问数据库的主要方法就是在BaseDao类里实现的。
参数化查询
简单的拼接SQL很容易,但要避免一些SQL注入等问题,就必须要使用参数化的查询,即使遇到一些像like等的查询没办法一定要拼接字符串的时候,也要在基类里集中进行处理,一个BreastDAL数据库访问类如下:
BreastDAL数据访问类
BaseDao是一个泛型类,占位符T是约束了实体类必须由BaseBean派生,因此在基类就可预先获取实体类的元数据注解,以便生成SQL语句,另外JoinTable(表关联)、Expression(条件)、Order(排序)等作为生成SQL的元素,为的是尽量不要直接写SQL语句,而像hibernate框架中专门有HQL语言,这是一种为完全面向对象查询的有意识设计,当然一些非常复杂的SQL语句还得直接写SQL或改用存储过程才行。
不同数据库和分页实现
为了提供不同数据库的访问,特别是SQL语法和主键的一些差异,需要提供良好的配置接口,以达到不改SQL而兼容各种数据库的访问,基类需要根据不同数据库进行不同处理,最大的难度可能是服务器分页处理上了,因为Access、SQL2000、SQL2005、Oracle、MYSQL分页几乎各不一样,这就必须要解藕一一实现,由于我目前只实现了Access的分页,且一些效率问题还没有经过测试,所以这里就不深入撰述了,不过以后的SQL2005、Oracle、MYSQL分页实现稍微都会简单些,因为都有内置分页函数支持。
代码生成
凡是企业级的开发,都会讲究效率问题,特别是针对自己的框架特点,做一个简单的代码生成工具自动生成一些Bean和Dao还是很有必要的,也就是根据数据库表结构生成对应的CS文件。Access数据库有点麻烦,先需要进入数据库,勾选工具>选项>视图>显示隐藏对象,然后再工具>安全>用户与组权限>选中对象名称MSysObjects,在权限里至少勾选读取数据和读取设计选项,这样才能读取Access数据库里的所有表,至于列嘛可以不必直接再去查询,因为查询表的数据里也会包含所有列信息,并且可以获取对应的.Net数据类型,所以生成代码的时候就没必要映射据类型了,简单处理就好。