使用EJB缓存JPA实体

时间:2011-10-28 12:59:33

标签: jpa ejb jpa-2.0 ejb-3.1

[真正的问题标记为粗体向下。以下是对我的情况的尽可能简短的解释]

我有以下JPA实体:

@Entity Genre{
 private String name;
 @OneToMany(mappedBy = "genre", cascade={CascadeType.MERGE, CascadeType.PERSIST})
 private Collection<Novel> novels;
}
@Entity
class Novel{
 @ManyToOne(cascade={CascadeType.MERGE, CascadeType.PERSIST})
 private Genre genre;
 private String titleUnique;
 @OneToMany(mappedBy="novel", cascade={CascadeType.MERGE, CascadeType.PERSIST})
 private Collection<NovelEdition> editions;
}
@Entity
class NovelEdition{
 private String publisherNameUnique;
 private String year;
 @ManyToOne(optional=false, cascade={CascadeType.PERSIST, CascadeType.MERGE})
 private Novel novel;
 @ManyToOne(optional=false, cascade={CascadeType.MERGE, CascadeType.PERSIST})
 private Catalog appearsInCatalog;
}
@Entity
class Catalog{
 private String name;
 @OneToMany(mappedBy = "appearsInCatalog", cascade = {CascadeType.MERGE, CascadeType.PERSIST})
 private Collection<NovelEdition> novelsInCatalog;
}

这个想法是拥有几个 Novels ,属于特定的类型,其中可以存在多个版本(不同的出版商,年份等)。为了简洁起见, NovelEdition 可以属于just 一个目录,就是这样一个由这样的文本文件代表的目录:

Catalog: Name Of Catalog 1
-----------------------
"Title of Novel 1", "Genre1 name","Publisher1 Name", 2009
"Title of Novel 2", "Genre1 name","Pulisher2 Name", 2010
.....

Catalog: Name Of Catalog 2
-----------------------
"Title of Novel 1", "Genre1 name","Publisher2 Name", 2011
"Title of Novel 2", "Genre1 name","Pulisher1 Name", 2011
.....

每个实体都使用Transaction Scoped EntityManager关联一个充当DAO的无状态EJB。例如:

@Stateless
public class NovelDAO extends AbstractDAO<Novel> {
    @PersistenceContext(unitName = "XXX")
    private EntityManager em;

    protected EntityManager getEntityManager() {
        return em;
    }

    public NovelDAO() {
        super(Novel.class);
    }
    //NovelDAO Specific methods
}

我感兴趣的是解析目录文件并构建相应的实体(我通常一次读取整批目录)。

作为解析字符串驱动的过程,我不想重复像 novelDAO.getByName(“Novel of Novel 1”)之类的操作,所以我想要集中使用缓存String-Identifier-&gt; Entity对象类型的映射。

目前我使用3个对象: 1)文件解析器,它执行以下操作:

final CatalogBuilder catalogBuilder = //JNDI Lookup
//for each file:
String catalogName = parseCatalogName(file);
catalogBuilder.setCatalogName(catalogName);
//For each novel edition
String title= parseNovelTitle();
String genre= parseGenre();
...
catalogBuilder.addNovelEdition(title, genre, publisher, year);
//End foreach
catalogBuilder.build();

2)CatalogBu​​ilder是一个有状态EJB,它使用Cache并在每次解析新目录文件时重新初始化,并在目录持久化后被“删除”。

@Stateful
public class CatalogBuilder {

    @PersistenceContext(unitName = "XXX", type = PersistenceContextType.EXTENDED)
    private EntityManager em;
    @EJB
    private Cache cache;
    private Catalog catalog;

    @PostConstruct
    public void initialize() {
      catalog = new Catalog();
      catalog.setNovelsInCatalog(new ArrayList<NovelEdition>());
    }

    public void addNovelEdition(String title, String genreStr, String publisher, String year){
        Genre genre = cache.findGenreCreateIfAbsent(genreStr);//**
        Novel novel = cache.findNovelCreateIfAbsent(title, genre);//**
        NovelEdition novEd = new NovelEdition();
        novEd.setNovel(novel);
        //novEd.set publisher year catalog
        catalog.getNovelsInCatalog().add();
    }

    public void setCatalogName(String name) {
        catalog.setName(name);
    }

    @Remove
    public void build(){
        em.merge(catalog);
    }
}

3)最后,有问题的bean:Cache 。对于CatalogBu​​ilder,我使用了一个EXTENDED持久化上下文(我需要它作为Parser执行几个成功的事务)和一个有状态EJB; 但在这种情况下,我不确定我需要什么。实际上,缓存

  1. 应该留在内存中,直到解析器完成其工作, 但不会更长(不应该是单身),因为解析只是一个 非常特殊的活动很少发生。
  2. 应该保留所有 上下文中的实体,应该返回托管实体表单 标有* 的方法,否则尝试持久保存目录 应该失败(重复INSERT).. *
  3. 应该使用相同的 持久化上下文作为CatalogBu​​ilder。
  4. 我现在拥有的是:

    @Stateful
    public class Cache {
    
       @PersistenceContext(unitName = "XXX", type = PersistenceContextType.EXTENDED)
        private EntityManager em;
    
        @EJB
        private sessionbean.GenreDAO genreDAO;
        //DAOs for other cached entities
    
        Map<String, Genre> genreName2Object=new TreeMap<String, Genre>();
    
        @PostConstruct
        public void initialize(){
        for (Genre g: genreDAO.findAll()) {
          genreName2Object.put(g.getName(), em.merge(g));
        }
        }
    
        public Genre findGenreCreateIfAbsent(String genreName){
         if (genreName2Object.containsKey(genreName){
           return genreName2Object.get(genreName);
        }
        Genre g = new Genre();
        g.setName();
        g.setNovels(new ArrayList<Novel>());
        genreDAO.persist(t);
        genreName2Object.put(t.getIdentifier(), em.merge(t));
    return t;
    }
    }
    

    但老实说我找不到同时满足这3点的解决方案。例如,使用另一个具有扩展持久化上下文的有状态bean 将适用于第一个已解析的文件,但我有 不知道第二个文件应该发生什么..确实对于第一个文件,PC将被创建并从 CatalogBu​​ilder 传播到 Cache ,然后将使用相同的PC 。但是在build()返回之后, CatalogBu​​ilder的PC应该(我猜)在连续解析过程中被删除并重新创建,尽管Cache的PC应该保持“活着”:在这种情况下不应该抛出异常吗?另一个问题是 Cache bean被钝化时该怎么办。目前我得到例外:

    "passivateEJB(), Exception caught -> 
    java.io.IOException: java.io.IOException
        at com.sun.ejb.base.io.IOUtils.serializeObject(IOUtils.java:101)
        at com.sun.ejb.containers.util.cache.LruSessionCache.saveStateToStore(LruSessionCache.java:501)"
    

    因此,我不知道如何实现我的缓存。你会如何解决这个问题?

1 个答案:

答案 0 :(得分:0)

EclipseLink配置:

  • 您可以在配置文件中为所有entites指定属性。

    <property name="eclipselink.cache.shared.default" value="true"/>

  • 在具有@Cache注释的实体级别,指定不同的属性。

常规JPA配置:

  • 在实体级别,使用@Cacheable注释,value属性为true

  • CacheRetrieveMode指定检索模式,属性USE启用其他BYPASS

    entityManager.setProperty("javax.persistence.cache.retrieveMode", CacheRetrieveMode.USE);

  • 使用CacheStoreModeBYPASSUSE属性REFRESH在缓存中配置存储空间。

    entityManager.setProperty("javax.persistence.cache.storeMode",CacheStoreMode.REFRESH);

  • Cache接口表示共享缓存。要删除所有或某些缓存实体,可以调用其中一个evict方法。您可以从EntityManagerFactory获取缓存接口的参考。