0 Replies Latest reply on Jan 28, 2010 4:07 PM by abuchholtz

    PersistenceContext flush mode changes for ExtendedDataModel

    abuchholtz

      Hi there,


      We've extended the ExtendedDataModel of Richfaces according to the blog entry at http://eclecticprogrammer.com/2008/07/30/a-generic-superclass-for-sorting-and-paginating-in-the-database-with-richfaces/ for a real DB pagination and sorting possibility. As the base PaginationDataModel class we are using is exactly the same I won't repost it here.


      The implementation works quite well. But unfortunately we had to change something in the underlying business logic that lead to a strange behavior:
      the persistence context flushmode gets somehow changed from the default value AUTO to MANUAL.


      What we do is



      • get the object list of OriginalSegments form the EM by calling findOriginalSegments(parameters) which creates a query based on the pagination parameters

      • theres another entity (TranslatedSegment) that has a one to many relation to the upper. We get the related object list by executing a query. If the list is empty we create one new TranslatedSegment and attach it to the OriginalSegment.

      • after that we call a check method that will call the same method for fetching the checking on an empty TranslatedSegment list.



      When we use a Seam @DataModel annotation for a ArrayList<OriginalSegment> everything works fine (except that we don't have real pagination of course).



      • the persistence bag for the TranslatedSegment list is marked dirty and right before the next query to get the list again the PC is flushed.



      When we switch to our implementation of the PaginationDataModel the flushmode is magically changed somehow to manual and the newly created TranslatedSegment is never flushed and we end in an infinite loop. As you cn see in method getTSFromEM() there is a check on the flush mode  and the temporary fix uncommented.


      We currently work around this with explicitly setting the flush mode of the PC before executing that particular query, but as we plan to use the PaginationDataModel at other places I would rather like to find the cause for this.


      I've already tried to set the PC flush mode manually with in a @Begin(join = true, flushMode = FlushModeType.AUTO) annotation on the findObjects() method of SegmentDataModel but without success.


      Some side notes on our environment



      • We are accessing the EM within our business logic through Component.getInstance(entityManager) in the method entityManager() of class EntityManagerGetter as our entities aren't Seam components and we therefore can't use injection.

      • we have nested conversations within our application



      To switch between our implementation and the default DataModel of Seam we edit the rich:dataTable to value="#{segmentDataModel}" .


      I would appreciate any help as i think there is something wrong in my understanding how Seam manages the persistence contexts.


      Here comes the relevant source code.


      SegmentDataModel for DB-pagination:



      @Name("SegmentDataModel")
      @Scope(ScopeType.CONVERSATION)
      public class SegmentDataModel extends PaginatingDataModel<OriginalSegment, Integer> {
           
           private Page currentPage;
           
           @Override
           public List<OriginalSegment> findObjects(int firstRow, int numberOfRows,
                     String sortField, boolean descending) {
                return currentPage.findOriginalSegments(firstRow, numberOfRows, sortField, descending);
           }
      
           @Override
           public String getDefaultSortField() {
                return "id";
           }
      
           @Override
           public Integer getId(OriginalSegment segment) {
                return segment.getId();
           }
      
           @Override
           public int getNumRecords() {
                return currentPage.getOriginalSegmentCount();
           }
      
           @Override
           public OriginalSegment getObjectById(Integer id) {
                return currentPage.getElementById(id);
           }
           
           @In(required = true, value = "contentPage")
           public void setCurrentPage(Page currentPage) {
                if (this.currentPage != currentPage) {
                     rowCount = null;
                     this.currentPage = currentPage;
                }
           }
           
           public Integer getCurrentPk(){
                return currentPk;
           }
           
           public void setCurrentPk(Integer currentPk){
                this.currentPk = currentPk;
           }
      }
      
      



      the entities OriginalSegment and TranslatedSegment reduced on the neccessary code:



      @Entity
      public class OriginalSegment extends EntityManagerGetter implements IOriginalSegment{
           @Id
           @GeneratedValue
           private int id;
           ...
      
           @OneToMany(cascade = CascadeType.ALL, mappedBy = "untranslatedSegment")
           @OrderBy
           private List<TranslatedSegment> translatedSegments = new ArrayList<TranslatedSegment>();
      
           @Override
           public List<ITranslatedSegment> getTranslatedSegments(IJobLanguage language) {
                return this.getTranslatedSegmentList(language);
           }
      
           private List<ITranslatedSegment> getTSfromEM(EntityManager em, IJobLanguage language) {
                if (PersistenceContexts.instance().getFlushMode() == FlushModeType.MANUAL) {
                     throw new RuntimeException("Flush mode is manual :(");
                     // This is an ugly hack to fix the bug
                     // PersistenceContexts.instance().changeFlushMode(FlushModeType.AUTO);
                }
                Query q = em.createQuery("select seg from TranslatedSegment seg " +
                          "where seg.untranslatedSegment = :untranslatedSegment and " +
                          "seg.jobLanguage = :language");
                q.setParameter("untranslatedSegment", this);
                q.setParameter("language", language);
                return q.getResultList();
           }
      
           public List<ITranslatedSegment> getTranslatedSegmentList(IJobLanguage language) {
                // gets the specific segments from the database.
                List<ITranslatedSegment> segments;
                segments = getTSfromEM(entityManager(), language);
                // create one empty TranslatedSegment if non yet exists for language
                if (segments.isEmpty()) {
                     TranslatedSegment transSeg = createTranslatedSegment(language);
      
                     transSeg.setChecked(true);
                     segments.add(transSeg);
                }
                return segments;
           }
           ...
      
      }





      @Entity
      public class TranslatedSegment extends EntityManagerGetter implements ITranslatedSegment {
           private boolean checked = false;
      
           @ManyToOne
           @JoinColumn(name = "untranslatedSegment", nullable = false)
           private OriginalSegment untranslatedSegment;
      
           ...
      
           @Override
           public void setChecked(boolean checked) {
                // set checked to false on all TranslatedSegments that belong to the same OriginalSegment to avoid that
                // 2 TranlatedSegments are checked at th same time for one OriginalSegment
                if (checked) {
                     List<ITranslatedSegment> allTranslatedSegments = untranslatedSegment.getTranslatedSegments(jobLanguage);
                     for (ITranslatedSegment ts : allTranslatedSegments) {
                          ((TranslatedSegment) ts).checked = false;
                     }
                }
                this.checked = checked;
           }
      
           ...
      }





      The backing bean with the usage of a default Seam @DataModel:


      @Name("jobDetailsEdit")
      @Scope(ScopeType.CONVERSATION)
      public class JobDetailsEdit implements IJobDetailsEdit, Serializable {
           @In(required = true, value = "contentPage")
           private IPage contentPage;
      
           private int itemsPerPage = 4;
      
           @DataModel
           private List<IOriginalSegments> segments= new ArrayList<IOriginalSegments>();
           ...
      
           @Override
           @Begin(join = true)
           public List<IOriginalSegments> getSegments() {
                return contentPage.getOriginalSegments();
           }
      
           @Override
           public void setSegments(List<IOriginalSegments> segments) {
                this.segments = segments;
           }
           ...
      }
      
      



      part of the view:


      <rich:dataTable value="#{jobDetailsEdit.segments}" var="_sourceSegment" rows="#{jobDetailsEdit.itemsPerPage}">
           <rich:column width="50%">
                <h:outputText value="#{_sourceSegment.content}" />
           </rich:column>
           <rich:column width="50%">
                <rich:dataGrid value="#{_sourceSegment.getTranslatedSegments(jobDetailsEdit.jobLanguage)}" var="_transSegment">
                     <h:outputText value="#{_transSegment.content}"/>
                </rich:dataGrid>
           </rich:column>
      </rich:dataTable>