[猫爪--综合] 一个自制简陋的持久层方案

Joard 2007-09-01
今天确实郁闷,打开电脑本想继续完善代码,
结果却发现代码尽然忘记放进u盘,着实郁闷啊!
今天代码就不贴代码了,过两天在补上。

在这里和大家探讨一下我对这个持久层的思路,想法和遇到的问题。
但是再开始之前,先向大家推荐两篇文章

《你擦了吗?确定擦了?真的确定擦了?》
http://www.iteye.com/article/13649

《一个自制持久层的方法》
http://www.iteye.com/topic/7068

毫无例外,这两篇文章都是ajoo牛以前的文章
其中《你擦了吗》是以前就拜读过好几次了,虽然文中比喻不雅,但也正好符合我等粗人。后一篇文章是写搜索到的,里面的思想对我这样的小菜鸟也挺受用,各位要是有时间也可以看看。

下面,我就直接说正题了

PersistentRecord
这就是我这个简陋持久层方案的名字

它的主要要求是
第一,简单,不需要任何xml文件,
唯一的配置文件就是一个数据库相关的properties文件

第二,支持部分功能的ORM
只支持单表的ORM,象多表查询,表间关联(对象之间的关系)这些都不支持。
但是对单表操作是支持ORM的。

第三,侵入性不太大,只要求你的数据库表是单一主键或使用代理主键。

第四,代码少(功能少,必然代码也多不到那里去)

第五,代码生成(后面就会提到)

第六,可测试性

说它简陋是因为持久层里面很重要的两个东西--缓存以及表间关联,我完全没考虑实现。虽然缓存这东西java里不错的现成解决方案可以选择,但是到现在还没打算是否为PersistentRecord加上缓存。至于表间关联,则完全是复杂度的考虑,不是说自己实现这个有多难,也不是担心实现的代码多么丑陋,而是没有太大必要,如果要用到表间关联,我看直接使用hibernate3+annotation就可以了,这个玩意上手也挺快地。少了这两个东西,复杂度下降不少啊。

写PersistentRecord的时候,主要是借鉴了rails和spring的daotemplate,hibernate3的annotation。整体上来说PersistentRecord是一个表记录封装框架,是一个以数据库表记录为中心的框架。第五条和第六条都是初步打算,通过代码生成来生成对应的类以及dao。可测试性也就是也一个junit测试类,让它在单元测试的时候自动导入数据和测试完后自动删除数据。

来一个简单的例子
加入数据库里有这样一张表
create table product (
id      int           not null,
title   varchar(100)  not null,
price   decimal(10,2) not null,
primary key (id)
)


通过generate生成这样一个类
@PersistentRecord
public class Product {
	private long id;
	
	private String title;
	
	private BigDecimal price;
	
	public long getId() {
		return id;
	}
	public void setId(long id) {
		this.id = id;
	}
	public String getTitle() {
		return titles;
	}
	public void setTitle(String title) {
		this.titles = title;
	}
	public BigDecimal getPrice() {
		return price;
	}
	public void setPrice(BigDecimal price) {
		this.price = price;
	}
}

这个Product类就体现出PersistentRecord的思路一:一个类对应一张表,类的成员变量对应表的列,每一个类的实例对应表中的一条记录。但在这里要求该表是一个单一主键的表(或者使用了代理主键)就象Product.id一样。

annotation上场了
如果Product对应的表或者表中的字段发生了变化,怎么办呢!
哈哈祭出annotation,让我们摆脱配置文件的束缚。PersistentRecord里提供了至少3种annotation。

PersistentRecord:就如上面代码里面所示,PersistentRecord这个annotation是一个标注型annotation,它负责告诉持久层的核心PersistentRecordManager“你现在处理的这个类是一个可以被持久化的PersistentRecord”,当然,这也表明如果你个bean不是一个用@PersistentRecord标注的类的话,你会在运行期得到一个异常提示你该类无法被持久化。

Table:如果对应的表发生了变换,那么这个Table就可以派上用场了
@Table(name="Product")
public class Product {

通过这样就可以改变类与之对应的表。

Column:同样数据库表的列发生了变化,这样些就可以了
@Column(name="title")
	private String titles;


最后变更后的代码会是这样
@PersistentRecord
@Table(name="Product")
public class Product {

	@Column(name="id",isPrimaryKey=true)
	private long key;
	
	@Column(name="title")
	private String titles;
	
	private BigDecimal price;
	
	public long getId() {
		return key;
	}
	public void setId(long id) {
		this.key = id;
	}
	public String getTitle() {
		return titles;
	}
	public void setTitle(String title) {
		this.titles = title;
	}
	public BigDecimal getPrice() {
		return price;
	}
	public void setPrice(BigDecimal price) {
		this.price = price;
	}
}

晕,不对呀,怎么多了一个“@Column(name="id",isPrimaryKey=true)”,其实我打算是这样地,PersistentRecord会默认id为主键,但如果你的表不是这样的时候,就可以如上述代码那样做,明确告诉PersistentRecord你的表的主键是哪一个,但同时isPrimaryKey=true 只能设置一次,否则你会得到一个异常。

好了,思路一大体上是这个样子了。接下来说说说说这个框架的核心类PersistentRecordManager

思路二,使用回调来处理数据库连接和事务
try{
		template.manageRecord(new ManageRecordCallback(){				
			public Object doInPersistentRecordManager(PersistentRecordManager manager) throws SQLException {
				Product newp = new Product();
				newp.setId(4);
				newp.setPrice(new BigDecimal(85));
				newp.setTitle("Unix Shell");
				manager.insert(newp);
					

				Product p = manager.get(Product.class,new Long(4));
				printProduct(p);
					
				manager.locked(p);
					
				p.setPrice(new BigDecimal(128.5));
				manager.update(p);
					
				p = manager.reload(p);
				printProduct(p);
					
				int count = manager.countByConditions(Product.class,"@key < ?", 5);
				System.out.println("Count_Product : " + count + "\n");
					
				manager.delete(p);
					
				List<Product> list = manager.findAllByConditions(Product.class, 
															"@key < ? and @price>?", 2, 80);
				if(list != null)
				for(Product tp : list){
					printProduct(tp);
				}
					
				Product tp = manager.find(Product.class,
							"find_by_titles_and_price", "Programming Ruby 2nd", 99);
				printProduct(tp);
					
				return null;
			}				
		});
	} catch (SQLException se) {
		se.printStackTrace();
	}

基本上提供的操作就是如此了,基本的CRUD就不说了,这这里就简单地说说三个函数:
countByConditions和findAllByConditions这两个函数的第一个参数是你要处理的类,第二个参数是conditions,第三个及以后的参数是实际查询的条件的值。关于第二个参数,多说两句。"@key < ? and @price>?",这里第一个参数所指定的类里的成员成员变量名前加上一个@符号,就可以生成正确的sql了,当然,你也可以直接些表里面的列名,但这样不太好,最好还是写类的成员变量名。

find方法和前两者类似,这里不太一样的是第二参数"find_by_titles_and_price",它强制要求传入的参数必须以find_by_开头,每个单词之间用and分开。(略了解Rails的大大们笑了,“这不就是ActiveRecord里面的计量吗?人家用method_missing人家用来实现,你就用字符串阿”),当然,这里的每个单词也是类的成员变量名。

要是觉得上面的代码太丑陋了,不用担心,以后用dao在封装一下,就能好看点了。

现在有一个问题征询一下大家的意见,现在有这样块代码
final Connection conn = getConnection();
	final Transaction trans = new Transaction(conn);
	try{  
		trans.beginTransaction();
		final PersistentRecordManager manager = new PersistentRecordManager(trans);
		Object result =  action.doInPersistentRecordManager(manager); 
		trans.commit();
		return result;
	} catch (SQLException se) {
		trans.rollback();
		throw se;
	} finally {
		DaoUtils.closeConnection(conn);
	} 

这段代码里事务是由框架负责提交地,我就在想到底该不该下放事务的更多自主权,而不是由框架来负责提交或回滚。例如根据业务需要来回滚事务,也需需要在事务完成后继续做一点事情?如果不该下放的话,那么我这里就会像spring那样实现一个TransactionStatus;如果该下放的话,怎么实现才能更好呢?
apolloty 2007-09-03
框架负责事务,然后rollback时给业务层返回状态即可
Global site tag (gtag.js) - Google Analytics