persistence.apt

来自「anewssystem新闻发布系统集成使用了spring hibernate f」· APT 代码 · 共 340 行

APT
340
字号
 ---
 persistence
 ---
 Lingo
 ---
 2007-06-21

dao数据访问层

 *springside中将dao,manager合并,并且省略了各自的接口,直接使用cglib实现AOP事务控制。

  我从前考虑的是,将dao,manager合并,但保留一个接口层,这样以后可以将实现替换成ibatis或jdbc,而不需要改动domain,controller,view层,但最近看到ibatis与hibernate的实现相差太远,勉强使用ibatis实现hibernate建立的数据模型,真不如重新设计一遍程序来得轻巧。决定以后即便重新制作几套系统,也要保证单个系统的简洁。以后将使用springside的方式,删除最后一层接口,如果以后真的有需要再增添这一层,但依照现在的经验,基本上是不会有这种问题了。

 *使用范型,让HibernateEntityDao不仅仅包含最基本的操作,连一些通用的业务逻辑也统统包含到基类中。

  当初自己在设计的时候还在考虑使用ibatis和jdbc的情况,使得很多功能都没有添加到基类中,也是因为自己的水平问题,很多功能点感觉控制不住,所以不敢加上。在制作样品管理的时候,也对domain做了NamedEntityBean和TreeEntityBean之类的加强。感觉不支持多重继承的确不方便,下一步的目标就整理这些扩展部分,包括springside的undeletable接口和annotation,自己也应该整理一套annotation。

hibernate

 现阶段,自己执手的项目,一定会绑定在hibernate上的。虽然自己对hibernate还不熟悉,依然会出现许多问题。

 *参考资料

  *官方参考手册

  *满江红对参考手册的中文翻译

  *还有没机会读到的unit test

 *映射

  *父子关系和多对多关系都是从springside学来的。

   使用hibernate-tools自动生成的annotation一直有问题,父子关系的变量名很难认,多对多关系总会出现embed类,似乎都无法正常使用。

   而且生成的代码没有javadoc,考虑在代码生成之后,再根据它生成的代码,重新生成自己的domain类。

  *继承映射,使用的是一个继承体系共用一张表的方式,因为之前遇到的入库出库,字段是完全相同的。

   遇到的问题是,在对父表搜索的时候,无法使用instanceof判断具体是哪个子类。只好使用entity.toString().indexOf("in") != -1判断,实在是难看死了。

*缓存

 *hibernate的session提供了一级缓存,在同一个session中对同一个id的对象load两次,并不会发送两条sql给数据库,但是session关闭的时候,一级缓存就失效了。

 *二级缓存是SessionFactory级别的全局缓存,在它下面可以使用不同的缓存类库,比如ehcache,oscache,默认是一直使用的ehcache,把cacheManager的shared配置成true,让他们使用同一个cache,可以减少一些异常。

 *基本配置

  hibernate.cache.use_query_cache=true

  hibernate.cache.provider_class=org.hibernate.cache.EhCacheProvider

 *entity级的二级缓存

  entity二级缓存是把po放进cache,缓存的key就是id,value是pojo。这与查询缓存不同。究竟怎么不同就不知道了。

  使用ehcache的时候需要配置ehcache.xml,否则会使用ehcache默认的配置。

+--
<cache name="com.xxx.pojo.Foo" maxElementsInMemory="500" eternal="false" timeToLiveSeconds="7200" timeToIdleSeconds="3600" overflowToDisk="true" />
+--

 *查询缓存

  上面的二级缓存是针对整个对象的,如果针对查询结果,就要用到下面的语句

+--
query.setCacheable(true); // 激活查询缓存
query.setCacheRegion("myCacheRegion"); // 指明要使用的cacheRegion,可选
+--

  cacheRegion是可选的,可以对使用的缓存进行特殊配置,在ehcache.xml里要补上一段

+--
<cache name="myCacheRegion" maxElementsInMemory="10" eternal="false" timeToLiveSeconds="7200" timeToIdleSeconds="3600" overflowDisk="true" />
+--

*Open Session In View

  为了解决lazyLoadException,加一个Spring带的Filter是最简单通用的方法

+--
<filter>
  <filter-name>hibernateFilter</filter-name>
  <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>hibernateFilter</filter-name>
  <url-pattern>*.htm</url-pattern>
</filter-mapping>
+--


*限制修改

  *设置pojo属性的update=false,则该属性插入数据库后不可修改。

  *返回Collection时设置unmodifiable

+--
public List<Category> getCategories() {
    return Collections.unmodifiableList(categories);
}
+--

*查询返回Map

+--
String hql = " select new map(goods.goodsNo as goodsNo,goods.code as code) from Goods goods"
+--

 即可以用Map进行访问Map goods = (Map) list.get(0); String code = (String) goods.get("code");

 对比直接用 result[0],result[1]访问的恐怖写法,或者定义一个新pojo的繁琐,返回map比较折衷。


*springside对hibernate的封装

 springside对hibernate进行了三层封装

 第一层:HibernateGenericDao,基于spring的HibernateDaoSupport,加入了分页方法与各种finder方法,并使用范型,避免了返回值强制类型转换。

 第二层:HibernateEntityDao,基于HibernateGenericDao,用范型声明dao所管理的entity类,默认拥有该entity的CRUD方法。

 第三层:HibernateExtendDao,基于HibernateEntityDao,扩展各种选择性功能。

**HibernateGenericDao

  *使用范型:

   使find(),get()这些方法不再返回Object,而是返回T,不再需要强制类型转换。

  *提供各种finder的简便方法:

   应用了JDK5可变参数的hsql查询方法:List find(String hsql, Object... values),支持find(hql), find(hql, param1), find(hql, param1, param2), find(hql, new Object[]{param1, param2})四种接口。

   简单查询的简化方法:findBy(Class entityClass, String name, Object value), findUniqueBy(Class entityClass, String name, Object value), findByLike(Class entityClass, String name, Object value)

  *获得设置好的Query和Criteria:

   createQuery(String hql, Object... values)和createCriteria(Class\<T\> entityClass, Criterion... criterions)

   spring并没有很好的接口封装支持firstResult, maxResult, fetchsize, cache, cacheRegion等多个查询参数,所以springside宁愿返回已设置好查询条件的Query和Criteria,让大家继续剩下的参数配置,最后再执行list(),注意那几个参数可以连续配置:

+--
createQuery(hql, param1).setFirstResult(10).setMaxResult(20).list();
+--

  *分页方法:

   Page pagedQuery(Criteria, int pageNo, int pageSize)和Page pagedQuery(String hql, int pageNo, int pageSize, Object... args)

   这些参数与springside-1.0中的extremetable不兼容,考虑是修改1.0中的util,还是增加对应的pagedQuery方法。

   Page是springside自行封装的一个典型Page类,pagedQuery与hibernate自身分页查询的差别是先运行一次count,获得符合条件的总记录数。

   如果查询不需要总记录数,用普通的hibernate api,加上setFirstResult(), setMaxResult()就解决,不需要pagedQuery()。

  *判别对象属性在数据库中唯一的方法:

   isUnique(Class\<T\> entityClass, Object entity, String names)

**HibernateEntityDao

  所有Manager之类,只管理一类对象的Manager类的基类,只需要在类定义处声明entity类型即可

+--
public class BookManager extends HibernateEntityDao<Book> {
}
+--

  通过\<Book\>的定义,避免了HibernateGenericDao类各方法中,必有的Class entityClass参数。

  如果需要操作其他的entity,比如BookManager可能需要处理Category(图书目录),可以注入CategoryManager。无需担心事务的问题,JavaEE的默认事务模型已能很好处理。

  如果没有对应的CategoryManager,或者各种原因不想注入的话,可以使用BookManager继承自HibernateGenericDao的带entity class参数的方法来操作Category的增删改,如Category category = this.get(Category.class, 1);

**HibernateExtendDao

  此类演示springside所做的一些扩展

  *支持对象不能被直接删除,只能设置状态列为无效

   接口UndeleteableEntityOperation,定义了要支持此功能必须实现的方法。

   可以有接口(UndeleteableEntity)和annotation(@Undeleteable)两种形式来定义无效列,annotation列形式还而已定义标识对象已删除的状态属性的名称,用接口则必须实现setStatus()方法,在里边操作实际的状态属性。

  *重载save(),在保存前先调用onValid()方法

  *增加find(Map map)方法

   默认查找map中全部条件相同的entity

   <条件的比较运算符默认为相同,用户也可以为属性名加上like_, largerthen_这样的前缀,则使用相应的运算符做比较(未完成)>

ibatis

*springside的封装

**ibatisGenericDao

  springside在spring的SqlMapClientDaoSupport基础上封装的dao,功能如下:

  *应用范型:

   使得find(), get()这些方法返回具体类型,不需要强制类型转换。

  *提供各种finder的简便方法:

   使用了JDK5可变参数的hql查询方法:List find(String hql, Object... values)

   简单查询的简化方法:findBy(Class entityClass, String name, Object value), findUniqueBy(Class entityClass, String name, Object value), findByLike(Class entityClass, String name, Object value)

  *分页函数:

   Page pagedQuery(Criteria criteria, int pageNo, int pageSize), Page pagedQuery(String hql, int pageNo, int pageSize, Object... args)

  *由于ibatis本身的特性,因此有少数方法的使用和HibernateGenericDao不太一样,如save传入的必须是一个制定操作方法的map。

**ibatisEntityDao

  同HibernateEntityDao。

*ibatis配置tips

 ibatis的xml映射里面有不少小窍门可以值得使用:

**使用namespace规划自己的xml映射文件

+--
<sqMap namespace="cn.uisoft.dao">
  <insert id="insertUserAccount" parameterClass="cn.uisoft.bean.UserAccount">
    .......
  </insert>
</sqlMap>
+--

**使用typeAlias

  上面的那个cn.uisoft.bean.UserAccount是不是太长?加上一个alias就可以在后面直接使用这个parameterClass="cn.uisoft.bean.UserAccount"了

+--
<typeAlias alias="UserAccount" type="cn.uisoft.bean.UserAccount">
+--

**使用默认集中map映射,比如map, int, string, 让程序更加灵活

+--
<select id="countUser" parameterClass="map" resultClass="int" >
  SELECT
    count(user_id)
  From
    game_users where user_type=#userType# and game_id=#gameId#
</select>
+--

**$与#

  上面都是使用#的,这个的特点是ibatis会根据反射自动加上一些sql符号,比如字符串的'',但是$就是不加任何符号,直接嵌入sql,比如String a = "Jack";

+--
SELECT
  count(user_id)
From
  game_users where user_type=#userType# and game_id=#gameId#
+--

  就ok,但是如果

+--
SELECT
  count(user_id)
From
  game_users where user_type=#userType# and game_id=$gameId$
+--

  就会因为sql语句是...game_id=Jack报错,因为sql语法要求game_id='Jack'表示字符串

  当然$因此可以直接参与sql逻辑,但是往往直接使用更容易出错,或者导致隐藏的类似于sql注入的漏洞。

  个人建议,自己做一个辅助类,可以类似于Criteria这种,自己包装生成用于$的sql(不过那样的话,可能要考虑各种数据的不同)

  以下是一个简单的$使用,根据不同的字段去取topten的数据

+--
<select id="getTopDB" parameterClass="map" resultClass="dbtopuser" >
  Use $dbName$
  SELECT
    username,nickname,$filedName$ as queryValue
  From
  game_users order by $filedName$ DESC
</select>
+--

**关于动态remapResults="true"的说明

  如果你使用下面这种动态语句的时候,在第二次传入参数tableName或者resultMap参数不同的时候会SQLException:Invalid column name错误,因为ibatis有自己的AutoResultMap的cache,它总是用上次的column去匹配表,解决的方法就是使用多个statement或者设置remapResults="true",就是每次会自动重新映射。

+--
<select id="selectTable" parameterClass="map" resultMap="$resultMap$" >
  Use $dbName$
  SELECT
    *
  From
    $tabileName$
</select>
+--

*JDBCTemplate

 当hql等查询方法不能满足性能或灵活性的要求,必须使用sql时,大家有三种选择:

 第一,使用Hibernate的sql查询函数,将查询结果对象转为entity对象。

 第二,使用Hibernate Session的getConnection()获得JDBC Connection,然后进行纯JDBC API操作

 第三,选择把spring的JDBCTemplate作为一种很不错的JDBC utils来使用

 JDBCTemplate的使用很简单,只要在ApplicationContext文件里定义一个jdbcTemplate节点,pojo获得注入后可以直接执行操作。不需要继承什么基类

 ApplicationContext定义

+--
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  <property name="dataSource" ref="dataSource"/>
</bean>
+--

 实际使用

+--
SqlRowSet rs = jdbcTemplate.queryForRowSet(sql, params);
+--

 Tips1: jdbcTemplate有很多的ORM化回调操作将返回结果转为对象列表,但很多时候还是需要返回ResultSet,spring有提供一个类似ResultSet的spring SqlRowSet对象。

 Tips2: 注意jdbcTemplate尽量只执行查询操作,莫要进行更新,否则很容易破坏hibernate的二级缓存体系。

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?