搜索
首页数据库mysql教程论基于数据访问的集合类(Data Access Based Collection)和领域

在正式展开之前,有一些概念要先做一个界定。首先: 领域 模型是指系统应对的 领域 中所有逻辑的一个抽象,本质上它是 领域 中各种对象和概念以及它们之间关系的 集合 。你可以用自然语言描述它,也可以用UML来描述,或者是代码去描述。特别地,当我们使用面

        在正式展开之前,有一些概念要先做一个界定。首先:领域模型是指系统应对的领域中所有逻辑的一个抽象,本质上它是领域中各种对象和概念以及它们之间关系的集合。你可以用自然语言描述它,也可以用UML来描述,或者是代码去描述。特别地,当我们使用面向对象建模技术来实现这个领域模型时,我们可以把这个实现出来的模型称之为对象模型。我们可以认为领域模型是一个概念模型,是分析阶段的产物。

  让精心构建的对象模型高效地工作有很多底层的技术问题需要解决,其中如何满足领域对象的业务方法在计算过程中对数据的需求是一个普遍存在的问题(实际上,在实际应用中,我们会遇到更为复杂的情况,不只是有数据的需求,还可能出现对应用层面发生依赖)。对于这一问题,目前有两种模型可供借鉴,那就是基于数据访问集合类和领域事件模式


基于数据访问集合类(Data Access Based Collection)


  基于数据访问集合类是我在开发oobbs系统时设计的一种模式。这一模式通过一个抽象的接口来代表某一对象依赖的一组集合。当这一对象实例化时,一个基于数据访问集合实现类会注入到这个对象中,所有通过这一集合进行的操作,比如遍历,增删元素等都是被实现类转化成数据访问操作。基于数据访问集合类很像是一个缩水版的Repository。还是以Forum的public List getThreads()方法为例,我们认为getThreads是Forum的一个典型的业务方法,但是由于一个Forum拥有众多的Thread,这使得我们根本不容许一次将这个集合全部加载出来。即使是在hibernate这类提供了lazy和extra lazy加载机制ORM工具里,也无法避免当我们直得去迭代这一集合时,它们会被一次性全部加载。而另一方面,实际的应用请求也不会一次请求所有的Thread,更常见的情况是以分页的形式,一小批次一小批次地请求。因此基于数据访问集合模式使用一个集合接口做为一个占位符,并声明了一些基本的集合操作:比如返回某一区间内的子集(为分页而服务)和add,remove等操作,而实现类里,这些方法是以数据访问的方式实现的。下面是集合的接口定义。它看起来很像一个普通的集合
package oobbs.domainmodel;

import java.io.Serializable;
import java.util.List;

/**
 * The collection interface represents a set of objects, it's like the
 * java.util.Collection, however, there no real objects in this collection, it
 * only looks like a collection, its method's implementation is database access
 * operation! see <code>oobbs.infrastructure.persistence.AbstractHibernateCollection</code> 
 * @author laurence.geng
 */
public interface Collection<entity pk extends serializable owner> {
	void setOwner(Owner owner);

	void setOwnerName(String ownerName);

	/**
	 * Adds an object. This method will persist entity to database directly!
	 * @param e an entity instance. * @return the pK the generated primary key
	 * after insert into database.
	 */
	PK add(Entity e);

	void addAll(java.util.Collection<entity> c);

	/**
	 * Removes the entity. This method will remove this entity from database
	 * directly.
	 */
	void remove(Entity e);

	void removeAll(java.util.Collection<entity> c);

	boolean contains(Entity o);

	boolean isEmpty();

	int size();

	/**
	 * The most important method. It returns a subset of the whole collection.
	 * the returned subset is fetched from database by sql, hql or other data
	 * access way, The Collection itself never load all elements once time!
	 */
	List<entity> toList(int startIndex, int offset);

	void flush();
}</entity></entity></entity></entity>

  下面则是基本于hibernate的集合接口实现类。它实现了所有的基本的操作。在Forum类中就会这样一个字段以及相应的getter和setter:

	@Transient
	private Collection<thread long forum> forumThreads;

	@Autowired
	/**
	 * Sets ForumThreadCollection. 
	 * ForumThreadCollection is injected by this setter. When a collection instance injected, set this forum to its forum! 
	 */
	public void setForumThreads(@Qualifier("forumThreads") Collection<thread long forum> forumThreads) {
		this.forumThreads = forumThreads;
		this.forumThreads.setOwner(this);
		this.forumThreads.setOwnerName("forum");
	}

	/**
	 *  Gets this forum's thread collection.
	 */
	public Collection<thread long forum> getForumThreads() {
		return forumThreads;
	}</thread></thread></thread>

  其中注入的forumThreads对象是一个名为ForumThreadHibernateCollection的类,它继承了AbstractHibernateCollection类,因为没有特殊的需要,没有重写任何方法。而下面展示的是service中对这集合的一次使用:
List<thread> threads = forum.getForumThreads().toList(startThreadIndex,threadTotal);</thread>

  我们来分析一下Domain Collection这一模式的优劣。我认为它最大的优点在于它能够以一个字段的形式存在于单端关联对象中,这使得单端对象的定义饱满,完成符合并体现了一对多双向关联中双方依赖关系。这一点是使用hiberate映射无法实现的,因为我们不能在Forum中映射@OneToMany(mappedBy="forum") private Set threads;

  但是它的缺点也是非常明显并且似乎是无法克服的,那就是它只能用来表示直接关联的集合,如果单端对象想通过这一集合进一步遍历元素中更深层次的二级,三级集合时,domain collection就显得力不从心了。比方说:在论坛的首页上往往会罗列出各个Forum的一些基本信息,其中之一就是该Forum有多少帖子(Post),相应的,Forum对象会有这样一个方法public Long getPostCount();很显然,Post是Forum的二级集合,一个Forum需要先得到它的Thread集合,再从每个Thread中得到Post集合。我们可以为了这一方法再提供一个ForumPostHibernateCollcetion用来代表一个Forum的所有Post的集合,但是这个集合已经和模型的定义发生了偏离,因为并没有从Forum到Post的直接关联。而更加普遍的问题的是:我们会常常遇到某一个单端实体从它直接依赖的对象开始进行深度地导航(体现在SQL上就是对多个表的join操作),这时候我们不能为每一种导航而创建一个从出发点到结束点的domain collection。在这种情况下,获取数据必须通过另外一种方式进行了,那就是Domain Event模式


领域事件(Domain Event)

  Domain Event模式最初由udi dahan提出,发表在自己的博客上:http://www.udidahan.com/2009/06/14/domain-events-salvation/这一模式得到广泛的认可。它所要应对的正是将领域对象从对repository或service的依赖中解脱出来,避免让领域对象对这些设施产生直接依赖。它的做法就是当领域对象的业务方法需要依赖到这些对象时就发出一个事件,这个事件会被相应的对象监听到并做出处理。在我的oobbs系统中,我对这一模式做了一些改进,主要是消除静态方法和规范事件模型。在我的方案中,这一机制由这样几个角色:DomainEventDispatcher,DomainEvent和DomainEventListener.

  DomainEventDispatcher会以字段的形式存在于领域对象中,负责在业务方法中dispatch领域事件。下面是所有Dispatcher的基类:

package oobbs.domainmodel;

import java.util.HashMap;
import java.util.Map;

/**
 * The DomainEventDispatcher take charge of listener registration and dispatch
 * domain event to corresponding listener to handle. Usually, one dispatch per
 * domain object.
 */
public class DomainEventDispatcher {
	/** The listener map. all registered listeners are stored in this map. */
	protected Map<string domainobejctlistener> listeners = new HashMap<string domainobejctlistener>();

	/** * Adds a listener. * * @param listener the listener */
	public void addListener(DomainObejctListener listener) {
		listeners.put(listener.getName(), listener);
	}

	/** * Removes all listeners. */
	public void removeAllListeners() {
		listeners.clear();
	}
}</string></string>

一般来说一个领域对象会有一个对应的event dispatcher,这个dispatcher会有一组重载的dispatch方法,用于分发不同的领域事件。下面是oobbs中Forum对象的event dispatcher.

package oobbs.domainmodel.forum;

import oobbs.Constants;
import oobbs.domainmodel.DomainEventDispatcher;
import oobbs.domainmodel.ResultCollector;

/**
 * * The ForumEventDispatcher dispatch all events which about Forum object. * @author
 * laurence.geng
 */
public class ForumEventDispatcher extends DomainEventDispatcher {
	public void dispatch(GetForumThreadEvent event, ResultCollector result) {
		ForumListener forumListener = (ForumListener) listeners.get(Constants.FORUM_REPO_AS_FORUM_LISTENER);
		forumListener.handleGetForumThreadEvent(event, result);
	}

	public void dispatch(GetForumPostCountEvent event, ResultCollector result) {
		ForumListener forumListener = (ForumListener) listeners.get(Constants.FORUM_REPO_AS_FORUM_LISTENER);
		forumListener.handleGetFroumPostCountEvent(event, result);
	}

	public void dispatch(GetForumThreadCountEvent event, ResultCollector result) {
		ForumListener forumListener = (ForumListener) listeners.get(Constants.FORUM_REPO_AS_FORUM_LISTENER);
		forumListener.handleGetForumThreadCountEvent(event, result);
	}
}

系统中会有很多的domain event,下面是所有领域事件的基类:
package oobbs.domainmodel;

/**
 * The supper class of all domain events. all events should provide event
 * source, the domain object which fired this event.
 */
public class DomainEvent {
	/** The event source, the domain object which fired this event. */
	protected Object source;

	public DomainEvent(Object source) {
		super();
		this.source = source;
	}

	/** * Gets the event source. * * @return the event source */
	public Object getSource() {
		return source;
	}
}

下面就是刚才提到的例子中返回Forum某一部分(分页)Thread的事件,在这个事件中我们看到一个事件可以携带一些参数,供listener使用:
package oobbs.domainmodel.forum;

import oobbs.domainmodel.DomainEvent;

/** 
 * The Event that forum request to get its threads.
 * @author laurence.geng 
 */
public class GetForumThreadEvent extends DomainEvent {
	/** The start index of request thread. */
	private int startIndex;
	/** The count of request thread. */
	private int count;

	public GetForumThreadEvent(Forum source, int startIndex, int count) {
		super(source);
		this.startIndex = startIndex;
		this.count = count;
	}

	public int getStartIndex() {
		return startIndex;
	}

	public int getCount() {
		return count;
	}
}

而下面就是我们所有listener的基类:
package oobbs.domainmodel;

/**
 * The super class of all domain object listeners. it handles events from
 * domain objects. Each listener has to provide a name as key for registering
 * itself to dispatcher. 
 * @see DomainObejctEvent 
 * @author laurence.geng
 */
public interface DomainObejctListener {
	/** * Gets the name. * * @return the name */
	public String getName();
}

下面是Forum的listenerr接口,这个接口会有很多handle方法,代码只展示了一个。
package oobbs.domainmodel.forum;

import oobbs.domainmodel.DomainObejctListener;
import oobbs.domainmodel.ResultCollector;

/**
 * * The forum listener. It handles all events from Forum object. * * @see
 * ForumEvent * @author laurence.geng
 */
public interface ForumListener extends DomainObejctListener {
	/**
	 * * Handle the event that a forum requests to get its threads. * * @param
	 * event the event * @param result the result
	 */
	public void handleGetForumThreadEvent(GetForumThreadEvent event,ResultCollector result);
}

然后 是这个接口一个实现类,在oobbs中,做为forum的repository的实现类:ForumHibernateRepository,自然成为实现这一接口的最佳选择:
/**
 * * The Forum's repository with hibernate implementation, besides, it's a forum
 * listener which handle * all events come from forum object.
 */
public class ForumHibernateRepository extends AbstractHibernateRepository<forum long> implements ForumRepository {
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * oobbs.domainmodel.forum.ForumListener#handleGetForumThreadEvent(oobbs
	 * .domainmodel.forum.GetForumThreadEvent,
	 * oobbs.domainmodel.ResultCollector)
	 */
	@Overridepublic
	void handleGetForumThreadEvent(final GetForumThreadEvent event, ResultCollector result) {
		List<thread> threads = (List<thread>) getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
					@SuppressWarnings("unchecked")
					public Object doInHibernate(Session session) throws HibernateException, SQLException {
						logger.info("Start to load threads of forum.");
						// Get forum.
						String getForuumThreadHql = "from Thread as thread where thread.forum=:forum";
						List<thread> threads = (List<thread>) session
								.createQuery(getForuumThreadHql)
								.setCacheable(true)
								.setParameter("forum", event.getSource())
								.setFirstResult(event.getStartIndex())
								.setMaxResults(event.getCount()).list();
						return threads;
					}
				});
		result.add(threads);
	}
}
</thread></thread></thread></thread></forum>

最后我们看一看这一切是如被触发的。我们来看forum的这个方法:oobbs.domainmodel.forum.Forum.getThreads(int, int):
	public List<thread> getThreads(int startIndex, int count) {
		GetForumThreadEvent event = new GetForumThreadEvent(this, startIndex, count);
		ResultCollector result = new ResultCollector();
		forumEventDispatcher.dispatch(event, result);
		return (List<thread>) result.getUniqueResult();
	}</thread></thread>

  很简洁的四行代码:分别new一个event和result collector,然后用ForumEventDispatcher来dispatch这个事件,然后收集返回的处理结果。上述所有组件都是围绕这里而设计的,我们希望在领域对象的业务方法里不会出现任何repository或service,DomainEvent模式很好的满足了我们的需求,并且是以一种非常优雅而简洁的方式。

       当然,在上面讲述的整个机制中,我们漏掉了一环没有讲,那就是dispatcher是如何被实例化并完成注册listener工作的。由于oobbs使用了spring的IOC管理对象, dispatcher是由IOC创建并完成注册listener等初始化工作的。下面的类完成了这一系列工作:

package oobbs.infrastructure.appcontext;

import oobbs.domainmodel.DomainObejctListener;
import oobbs.domainmodel.forum.ForumEventDispatcher;
import oobbs.domainmodel.forum.ThreadEventDispatcher;
import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * * The ApplicationBeanPostProcessor will do some initialization work when bean
 * is created by spring ioc container, * such as: Adding listeners for domain
 * event dispatchers and so on. * @author laurence.geng
 */
public class ApplicationBeanPostProcessor implements BeanPostProcessor,
		ApplicationContextAware {
	/** The Constant logger. */
	private static final Logger logger = Logger
			.getLogger(ApplicationBeanPostProcessor.class);
	/** The application context. */
	private ApplicationContext applicationContext;

	/*
	 * (non-Javadoc) * @see org.springframework .beans.factory
	 * .config.BeanPostProcessor #postProcessBeforeInitialization
	 * (java.lang.Object, java.lang.String)
	 */

	public Object postProcessBeforeInitialization(Object bean, String beanName)	throws BeansException {
		return bean; // we could potentially return any object reference here...
	}

	/*
	 * (non-Javadoc) * @see
	 * org.springframework.beans.factory.config.BeanPostProcessor
	 * #postProcessAfterInitialization(java.lang.Object, java.lang.String)
	 */
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		logger.debug("Bean '" + beanName + "' created : " + bean.toString());
		// Adding listeners for domain event dispatchers.
		if ("forumEventDispatcher".equals(beanName)) {
			ForumEventDispatcher forumEventDispatcher = (ForumEventDispatcher) bean;
			forumEventDispatcher.addListener((DomainObejctListener) applicationContext.getBean("forumRepository"));
		}
		if ("threadEventDispatcher".equals(beanName)) {
			ThreadEventDispatcher threadEventDispatcher = (ThreadEventDispatcher) bean;
			threadEventDispatcher.addListener((DomainObejctListener) applicationContext.getBean("threadRepository"));
		}
		return bean;
	}

	/*
	 * (non-Javadoc) * @see
	 * org.springframework.context.ApplicationContextAware#setApplicationContext
	 * (org.springframework.context.ApplicationContext)
	 */
	@Override
	public void setApplicationContext(ApplicationContext arg0) throws BeansException {
		this.applicationContext = arg0;
	}
}


小结

  最后总结一下:其实基于数据访问集合类(Data Access Based Collection)和领域事件(Domain Event)两者各有优劣。在实际中可以结合使用。如果只使用数据访问集合类,则很难支持二、三级关联,而一味地使用领域事件则有会导致引入过多的事件类,引起类型爆炸。
声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
MySQL:初学者的基本技能MySQL:初学者的基本技能Apr 18, 2025 am 12:24 AM

MySQL适合初学者学习数据库技能。1.安装MySQL服务器和客户端工具。2.理解基本SQL查询,如SELECT。3.掌握数据操作:创建表、插入、更新、删除数据。4.学习高级技巧:子查询和窗口函数。5.调试和优化:检查语法、使用索引、避免SELECT*,并使用LIMIT。

MySQL:结构化数据和关系数据库MySQL:结构化数据和关系数据库Apr 18, 2025 am 12:22 AM

MySQL通过表结构和SQL查询高效管理结构化数据,并通过外键实现表间关系。1.创建表时定义数据格式和类型。2.使用外键建立表间关系。3.通过索引和查询优化提高性能。4.定期备份和监控数据库确保数据安全和性能优化。

MySQL:解释的关键功能和功能MySQL:解释的关键功能和功能Apr 18, 2025 am 12:17 AM

MySQL是一个开源的关系型数据库管理系统,广泛应用于Web开发。它的关键特性包括:1.支持多种存储引擎,如InnoDB和MyISAM,适用于不同场景;2.提供主从复制功能,利于负载均衡和数据备份;3.通过查询优化和索引使用提高查询效率。

SQL的目的:与MySQL数据库进行交互SQL的目的:与MySQL数据库进行交互Apr 18, 2025 am 12:12 AM

SQL用于与MySQL数据库交互,实现数据的增、删、改、查及数据库设计。1)SQL通过SELECT、INSERT、UPDATE、DELETE语句进行数据操作;2)使用CREATE、ALTER、DROP语句进行数据库设计和管理;3)复杂查询和数据分析通过SQL实现,提升业务决策效率。

初学者的MySQL:开始数据库管理初学者的MySQL:开始数据库管理Apr 18, 2025 am 12:10 AM

MySQL的基本操作包括创建数据库、表格,及使用SQL进行数据的CRUD操作。1.创建数据库:CREATEDATABASEmy_first_db;2.创建表格:CREATETABLEbooks(idINTAUTO_INCREMENTPRIMARYKEY,titleVARCHAR(100)NOTNULL,authorVARCHAR(100)NOTNULL,published_yearINT);3.插入数据:INSERTINTObooks(title,author,published_year)VA

MySQL的角色:Web应用程序中的数据库MySQL的角色:Web应用程序中的数据库Apr 17, 2025 am 12:23 AM

MySQL在Web应用中的主要作用是存储和管理数据。1.MySQL高效处理用户信息、产品目录和交易记录等数据。2.通过SQL查询,开发者能从数据库提取信息生成动态内容。3.MySQL基于客户端-服务器模型工作,确保查询速度可接受。

mysql:构建您的第一个数据库mysql:构建您的第一个数据库Apr 17, 2025 am 12:22 AM

构建MySQL数据库的步骤包括:1.创建数据库和表,2.插入数据,3.进行查询。首先,使用CREATEDATABASE和CREATETABLE语句创建数据库和表,然后用INSERTINTO语句插入数据,最后用SELECT语句查询数据。

MySQL:一种对数据存储的初学者友好方法MySQL:一种对数据存储的初学者友好方法Apr 17, 2025 am 12:21 AM

MySQL适合初学者,因为它易用且功能强大。1.MySQL是关系型数据库,使用SQL进行CRUD操作。2.安装简单,需配置root用户密码。3.使用INSERT、UPDATE、DELETE、SELECT进行数据操作。4.复杂查询可使用ORDERBY、WHERE和JOIN。5.调试需检查语法,使用EXPLAIN分析查询。6.优化建议包括使用索引、选择合适数据类型和良好编程习惯。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前By尊渡假赌尊渡假赌尊渡假赌
威尔R.E.P.O.有交叉游戏吗?
1 个月前By尊渡假赌尊渡假赌尊渡假赌

热工具

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版