2014-03-05

PostgreSQL performance improvement for WHERE, ORDER BY and LIMIT


PostgreSQL has a "strange" behaviour for the following database table and select.

Table:
CREATE TABLE example.person
(
  id bigint NOT NULL,
  lastname character varying(255) NOT NULL,
  CONSTRAINT person_pkey PRIMARY KEY (id)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE example.person
  OWNER TO postgres;

-- Index: example.person_lastname

-- DROP INDEX example.person_lastname;

CREATE INDEX person_lastname
  ON example.person
  USING btree
  (lastname);


Select:
select * from example.person where lastname ilike 'V%' order by lastname asc limit 1

select * from example.person where lastname ilike 'V%' order by lastname desc limit 1

The table has about 600 thousand entries and the query planner is absolutely equal as you can see:

explain select * from example.person where lastname ilike 'V%' order by lastname asc limit 1
"Limit  (cost=0.00..0.08 rows=1 width=61)"
"  ->  Index Scan using person_lastname on person  (cost=0.00..54683.17 rows=653276 width=61)"
"        Filter: ((lastname)::text ~~* 'V%'::text)"

explain select * from example.person where lastname ilike 'V%' order by lastname desc limit 1 
"Limit  (cost=0.00..0.08 rows=1 width=61)"
"  ->  Index Scan Backward using person_lastname on person  (cost=0.00..54683.17 rows=653276 width=61)"
"        Filter: ((lastname)::text ~~* 'V%'::text)"

The problem is that the first statement takes about 3.5 sec compared to the second one which only needs 0.6 sec.

The EXPLAIN ANALYSE shows different results which indicates that the database doesn't execute the queries similar.

explain analyse select * from example.person where lastname ilike 'V%' order by lastname asc limit 1
"Limit  (cost=0.00..0.08 rows=1 width=61) (actual time=3469.829..3469.829 rows=1 loops=1)"
"  ->  Index Scan using person_lastname on person  (cost=0.00..54683.17 rows=653276 width=61) (actual time=3469.827..3469.827 rows=1 loops=1)"
"        Filter: ((lastname)::text ~~* 'V%'::text)"
"Total runtime: 3469.853 ms"

explain analyse select * from example.person where lastname ilike 'V%' order by lastname desc limit 1
"Limit  (cost=0.00..0.08 rows=1 width=61) (actual time=670.210..670.210 rows=1 loops=1)"
"  ->  Index Scan Backward using person_lastname on person  (cost=0.00..54683.17 rows=653276 width=61) (actual time=670.208..670.208 rows=1 loops=1)"
"        Filter: ((lastname)::text ~~* 'V%'::text)"
"Total runtime: 670.233 ms"

The solution for the problem is a different index because PostgreSQL allows the definition of several operator classes. In that case the index would look like:

CREATE INDEX person_lastname
  ON example.person
  USING btree
  (lastname varchar_pattern_ops);

Now both select statements need 0.6 sec.


Tested with following PostgreSQL Databases:
  • 8.3
  • 9.2

2012-11-08

Is Struts (still) the most popular Java web framework?

I assume that Google search terms and Job statistics can indicate which framework is the most popular one.

The next figure shows that Struts and JSF are the two highest-ranking Java web frameworks. The other ones play only a minority role. It also demonstrates that Struts and JSF are both declining in the last few years. Which framework will get ahead first? Or do I miss a framework which has already passed Struts and JSF?



I also checked Struts compared to Play!, Roma, Tapestry, Trails and WebWork but none of them can gain better results than Grails or Wicket.

If we examine job statistics from Indeed.com than Struts is the clear number one. See the results:

2010-03-16

Richfaces DataTable: Server Side Pagination, Sorting and Filtering

PaginatingDataModel.java

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.el.Expression;
import javax.faces.context.FacesContext;

import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.Range;
import org.ajax4jsf.model.SequenceRange;
import org.ajax4jsf.model.SerializableDataModel;
import org.richfaces.model.ExtendedFilterField;
import org.richfaces.model.FilterField;
import org.richfaces.model.Modifiable;
import org.richfaces.model.Ordering;
import org.richfaces.model.SortField2;

public abstract class PaginatingDataModel<T, U> extends SerializableDataModel implements Modifiable {

    private static final long serialVersionUID = 2954923950179861809L;

    protected U currentPk;
    
    protected int rowIndex;

    protected boolean descending = true;

    protected String sortField = null;
    
    protected HashMap<String,Object> filterMap = new HashMap<String,Object>();

    protected boolean detached = false;

    protected List<U> wrappedKeys = new ArrayList<U>();

    protected Integer rowCount;

    protected Map<U, T> wrappedData = new HashMap<U, T>();

    /**
     * 
     * @see org.ajax4jsf.model.ExtendedDataModel#getRowKey()
     */
    @Override
    public Object getRowKey() {
        return currentPk;
    }

    /**
     * 
     * @see org.ajax4jsf.model.ExtendedDataModel#setRowKey(java.lang.Object)
     */

    @SuppressWarnings("unchecked")
    @Override
    public void setRowKey(final Object key) {
        this.currentPk = (U) key;
    }

    /**
     * 
     * @see org.ajax4jsf.model.SerializableDataModel#update()
     */
    @Override
    public void update() {
        detached = false;
    }

    /**
     * 
     * @see org.ajax4jsf.model.ExtendedDataModel#getSerializableModel(org.ajax4jsf.model.Range)
     */
    @Override
    public SerializableDataModel getSerializableModel(final Range range) {
        if (wrappedKeys != null) {
            detached = true;
            return this;
        }
        return null;
    }

    /**
     * 
     * @see javax.faces.model.DataModel#setRowIndex(int)
     */
    @Override
    public void setRowIndex(final int arg0) {
        rowIndex = arg0;
    }

    /**
     * 
     * @see javax.faces.model.DataModel#setWrappedData(java.lang.Object)
     */
    @Override
    public void setWrappedData(final Object data) {
        throw new UnsupportedOperationException();
    }

    /**
     * 
     * @see javax.faces.model.DataModel#getRowIndex()
     */
    @Override
    public int getRowIndex() {
        return rowIndex;
    }

    /**
     * 
     * @see javax.faces.model.DataModel#getWrappedData()
     */
    @Override
    public Object getWrappedData() {
        throw new UnsupportedOperationException();
    }

    /**
     * 
     * @see org.ajax4jsf.model.ExtendedDataModel#walk(javax.faces.context.FacesContext, org.ajax4jsf.model.DataVisitor,
     *      org.ajax4jsf.model.Range, java.lang.Object)
     */
    @Override
    public void walk(final FacesContext context, final DataVisitor visitor, final Range range, final Object argument)
            throws IOException {
        final int firstRow = ((SequenceRange) range).getFirstRow();
        final int numberOfRows = ((SequenceRange) range).getRows();
        if (detached) {
            for (final U key : wrappedKeys) {
                setRowKey(key);
                visitor.process(context, key, argument);
            }
        } else { // if not serialized, than we request data from data provider
            wrappedKeys = new ArrayList<U>();
            for (final T object : findObjects(firstRow, numberOfRows, sortField, filterMap, descending)) {
                wrappedKeys.add(getId(object));
                wrappedData.put(getId(object), object);
                visitor.process(context, getId(object), argument);
            }
        }
    }

    /**
     * 
     * @see javax.faces.model.DataModel#isRowAvailable()
     */

    @Override
    public boolean isRowAvailable() {
        if (currentPk == null) {
            return false;
        }
        if (wrappedKeys.contains(currentPk)) {
            return true;
        }
        if (wrappedData.entrySet().contains(currentPk)) {
            return true;
        }
        try {
            if (getObjectById(currentPk) != null) {
                return true;
            }
        } catch (final Exception e) {
        }
        return false;
    }

    /**
     * 
     * @see javax.faces.model.DataModel#getRowData()
     */
    @Override
    public Object getRowData() {
        if (currentPk == null) {
            return null;
        }
        T object = wrappedData.get(currentPk);
        if (object == null) {
            object = getObjectById(currentPk);
            wrappedData.put(currentPk, object);
        }
        return object;
    }

    /**
     * 
     * @see javax.faces.model.DataModel#getRowCount()
     */
    @Override
    public int getRowCount() {
        if (rowCount == null) {
            rowCount = getNumRecords(filterMap).intValue();
        }
        return rowCount;
    }

    @Override
    public void modify(List<FilterField> filterFields, List<SortField2> sortFields) {
        filterMap.clear();
        SortField2 sortField2 = null;
        String expressionStr = null;
        ExtendedFilterField extendedFilterField = null;
        Expression expression = null;
        String value = null;
        if (sortFields != null && !sortFields.isEmpty()) {
            sortField2 = sortFields.get(0);
            expression = sortField2.getExpression();
            expressionStr = expression.getExpressionString();
            if (!expression.isLiteralText()) {
                expressionStr = expressionStr.replaceAll("[#|$]{1}\\{.*?\\.", "").replaceAll("\\}", "");
            }
            this.sortField = expressionStr;
            if (sortField2.getOrdering() == Ordering.DESCENDING) {
                this.descending = true;
            } else {
                this.descending = false;
            }
        }
        if (filterFields != null && !filterFields.isEmpty()) {
            for (FilterField filterField : filterFields) {
                if (filterField instanceof ExtendedFilterField) {
                    extendedFilterField = (ExtendedFilterField) filterField;
                    value = extendedFilterField.getFilterValue();
                    if (value != null && !value.equals("")) {
                        expression = extendedFilterField.getExpression();
                        expressionStr = expression.getExpressionString();
                        if (!expression.isLiteralText()) {
                            expressionStr = expressionStr.replaceAll("[#|$]{1}\\{.*?\\.", "").replaceAll("\\}", "");
                        }
                        filterMap.put(expressionStr, value);
                    }
                }
            }
        }
    }

    /**
     * @param object
     * 
     * @return U
     */
    public abstract U getId(T object);

    /**
     * 
     * @param firstRow
     * 
     * @param numberOfRows
     * 
     * @param sortField
     * 
     * @param descending
     * 
     * @return List<T>
     */
    public abstract List<T> findObjects(int firstRow, int numberOfRows, String sortField, HashMap<String,Object> filterMap, boolean descending);

    /**
     * 
     * @param id
     * 
     * @return T
     */
    public abstract T getObjectById(U id);

    /**
     * 
     * @return Long
     */
    public abstract Long getNumRecords(HashMap<String,Object> filterMap);

}

PersonPaginatingDataModel.java

import java.util.HashMap;
import java.util.List;

import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;

import at.ooegkk.svnet.juhealth.entity.Person;
import at.ooegkk.svnet.juhealth.service.PersonService;

@Name("personPaginatingDataModel")
public class PersonPaginatingDataModel extends PaginatingDataModel<Person, Long> {
    
    private static final long serialVersionUID = 1868068053701471139L;
    
    @In(required = false)
    private PersonService personService;

    /**
     * @see PaginatingDataModel#getId
     */
    @Override
    public Long getId(Person person) {
        return person.getId();
    }

    /**
     * @see PaginatingDataModel#findObjects
     */
    @Override
    public List<Person> findObjects(int firstRow, int maxResult, String sortField, HashMap<String,Object> filterMap, boolean descending) {
        return personService.getRange(firstRow, maxResult, sortField, filterMap, descending);
    }

    /**
     * @see PaginatingDataModel#getObjectById(java.lang.Object)
     */
    @Override
    public Person getObjectById(Long id) {
        return personService.getPersonById(id);
    }

    /**
     * 
     * @see PaginatingDataModel#getNumRecords
     */
    @Override
    public Long getNumRecords(HashMap<String,Object> filterMap) {
        return personService.getCount(filterMap);
    }
}

PersonService.java

import java.util.HashMap;
import java.util.List;

import javax.ejb.Remote;

import at.ooegkk.svnet.juhealth.entity.Person;

@Remote
public interface PersonService {

 public List<Person> findAllPersons();
 public void save(Person p);
 public void merge(Person p);
 public void remove(Person p);
 public Person getPersonById(Long id);
 public Long getCount(HashMap<String,Object> filterMap);
 public List<Person> getRange(Integer firstRow, Integer maxResult, String sortField, HashMap<String,Object> filterMap, boolean descending);
    
}

PersonServiceImpl.java

import java.util.HashMap;
import java.util.List;

import javax.ejb.EJB;
import javax.ejb.Stateless;

import at.ooegkk.svnet.juhealth.dao.GenericDAO;
import at.ooegkk.svnet.juhealth.dao.PersonDAO;
import at.ooegkk.svnet.juhealth.entity.Person;
import at.ooegkk.svnet.juhealth.service.PersonService;

@Stateless
@SuppressWarnings("unchecked")
public class PersonServiceImpl implements PersonService {

 @EJB(name="svnet-juhealth-EAR/PersonDAOImpl/local")
 private PersonDAO personDAOImpl;
 
 public List<Person> findAllPersons() {
  List<Person> allPersons = personDAOImpl.findAll(Person.class);
  return allPersons;
 }
 
 public void save(Person person) {
  personDAOImpl.persist(person);
 }

 public void merge(Person person) {
  personDAOImpl.merge(person);
 }
 
 public void remove(Person person) {
  personDAOImpl.remove(person);
 }
 
 public Person getPersonById(Long id) {
  Person p = (Person) personDAOImpl.findById(Person.class, id);
  return p;
 }
 
 public Long getCount(HashMap<String,Object> filterMap) {
        Long count = personDAOImpl.getCount(Person.class, filterMap);
        return count;
    }
    
    public List<Person> getRange(Integer firstRow, Integer maxResult, String sortField, HashMap<String,Object> filterMap, boolean descending) {
        List<Person> list = null;
        if (descending) {
            list = personDAOImpl.findByFilter(Person.class, firstRow, maxResult, sortField, GenericDAO.SORT_ORDER.DESC, filterMap);
        } else {
            list = personDAOImpl.findByFilter(Person.class, firstRow, maxResult, sortField, GenericDAO.SORT_ORDER.ASC, filterMap);
        }
        return list;
    }

}

GenericDAO.java

package at.ooegkk.svnet.juhealth.dao;

import java.util.HashMap;
import java.util.List;
import java.util.Set;

import javax.ejb.Local;

@SuppressWarnings("unchecked")
@Local
public interface GenericDAO {

    public enum SORT_ORDER {
        ASC, DESC
    };

    public Object findById(Class entityClass, long id);

    public List findAll(Class entityClass);

    public List findAll(Class entityClass, String orderByAttribute, SORT_ORDER sortOrder);

    public List findAll(Class entityClass, int maxResult);

    public List findAll(Class entityClass, int firstRow, int maxResult);

    public List findAll(Class entityClass, int maxResult, String orderByAttribute, SORT_ORDER sortOrder);

    public void persist(Object entity, boolean flushImmediate);

    public void persist(Object entity);

    public Object merge(Object entity, boolean flushImmediate);

    public Object merge(Object entity);

    public void remove(Object entity, boolean flushImmediate);

    public void remove(Object entity);

    public Set<String> exludeNames();

    public Long getCount(Class entityClass);
    
    public Long getCount(Class entityClass, HashMap<String,Object> filterMap);

    public List findByFilter(Class entityClass, int firstRow, int maxResult, String orderByAttribute, SORT_ORDER sortOrder,
            HashMap<String, Object> filterMap);

}

GenericDAOImpl.java

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import at.ooegkk.svnet.juhealth.dao.GenericDAO;

@SuppressWarnings("unchecked")
@Stateless
public class GenericDAOImpl implements GenericDAO {
 
 @PersistenceContext(unitName="svnet-juhealth")
 private EntityManager entityManager;

 public EntityManager getEntityManager() {
  return entityManager;
 }

 public void setEntityManager(EntityManager entityManager) {
  this.entityManager = entityManager;
 }

 public Object findById(Class entityClass, long id) {
  return getEntityManager().find(entityClass, id);
 }

 public List findAll(Class entityClass) {
  return getEntityManager().createQuery(
    "select e from " + entityClass.getName() + " e ")
    .getResultList();
 }

 public List findAll(Class entityClass, 
   String orderByAttribute, SORT_ORDER sortOrder) {
  String sortOrderString = "desc";
  if (sortOrder == SORT_ORDER.ASC) {
   sortOrderString = "asc";
  }
  return getEntityManager().createQuery(
    "select e from " + entityClass.getName() + " e order by " +
    orderByAttribute + " " + sortOrderString).getResultList();
 }

 public List findAll(Class entityClass, int maxResult) {
  Query q = getEntityManager().createQuery(
    "select e from " + entityClass.getName() + " e ");
  if (maxResult > 0) {
   q.setMaxResults(maxResult);
  }
  return q.getResultList();
 }
 
 public List findAll(Class entityClass, int firstRow, int maxResult) {
        Query q = getEntityManager().createQuery(
                "select e from " + entityClass.getName() + " e ");
        if (firstRow > 0) {
            q.setFirstResult(firstRow);
        }
        if (maxResult > 0) {
            q.setMaxResults(maxResult);
        }
        return q.getResultList();
    }

 public List findAll(Class entityClass, int maxResult,
   String orderByAttribute, SORT_ORDER sortOrder) {
  String sortOrderString = "desc";
  if (sortOrder == SORT_ORDER.ASC) {
   sortOrderString = "asc";
  }
  Query q = getEntityManager().createQuery(
    "select e from " + entityClass.getName() + " e order by "
      + orderByAttribute + " " + sortOrderString);
  if (maxResult > 0) {
   q.setMaxResults(maxResult);
  }
  return q.getResultList();
 }

 public void persist(Object entity, boolean flushImmediate) {
  getEntityManager().persist(entity);
  if (flushImmediate)
   getEntityManager().flush();
 }

 public void persist(Object entity) {
  this.persist(entity, false);
 }

 public Object merge(Object entity, boolean flushImmediate) {
  Object obj = getEntityManager().merge(entity);
  if (flushImmediate)
   getEntityManager().flush();
  return obj;
 }

 public Object merge(Object entity) {
  return this.merge(entity, false);
 }

 public void remove(Object entity, boolean flushImmediate) {
  getEntityManager().remove(getEntityManager().merge(entity));
  if (flushImmediate)
   getEntityManager().flush();
 }

 public void remove(Object entity) {
  this.remove(entity, false);
 }

 public Set<String> exludeNames() {
  return new HashSet<String>();
 }
 
 public Long getCount(Class entityClass) {
        Query q = getEntityManager().createQuery(
                "select count(e) from " + entityClass.getName() + " e ");
        return (Long) q.getSingleResult();
 }
 
 public Long getCount(Class entityClass, HashMap<String,Object> filterMap) {
     StringBuffer query = new StringBuffer();
     query.append("select count(e) from " + entityClass.getName() + " e");
     // where clause
        query.append(this.getWhereClause(filterMap));
        Query q = getEntityManager().createQuery(query.toString());
        // parameter
        q = this.getParameteredQuery(q, filterMap);
     return (Long) q.getSingleResult();
    }
 
 public List findByFilter(Class entityClass, int firstRow, int maxResult,
            String orderByAttribute, SORT_ORDER sortOrder, HashMap<String,Object> filterMap) {
     StringBuffer query = new StringBuffer();
     query.append("SELECT e FROM " + entityClass.getName() + " e");
     // sort order
        String sortOrderString = "desc";
        if (sortOrder == SORT_ORDER.ASC) {
            sortOrderString = "asc";
        }
        // where clause
        query.append(this.getWhereClause(filterMap));
        // order by
        if (orderByAttribute != null) {
            query.append(" ORDER BY " + orderByAttribute + " " + sortOrderString);
        }
        Query q = getEntityManager().createQuery(query.toString());
        // parameter
        q = this.getParameteredQuery(q, filterMap);
        if (firstRow > 0) {
            q.setFirstResult(firstRow);
        }
        if (maxResult > 0) {
            q.setMaxResults(maxResult);
        }
        return q.getResultList();
    }
 
 private String getWhereClause(HashMap<String,Object> filterMap) {
     StringBuffer query = new StringBuffer();
     if (filterMap != null && !filterMap.isEmpty()) {
            query.append(" WHERE");
            boolean first = true;
            for (String column : filterMap.keySet()) {
                if (first) {
                    first = false;
                } else {
                    query.append(" AND");
                }
                if (filterMap.get(column) instanceof String) {
                    query.append(" UPPER(e." + column + ") LIKE :" + column);
                } else {
                    query.append(" e." + column + " = :" + column);
                }
            }
        }
     return query.toString();
 }
 
 private Query getParameteredQuery(Query q, HashMap<String,Object> filterMap) {
     if (filterMap != null && !filterMap.isEmpty()) {
            for (String column : filterMap.keySet()) {
                if (filterMap.get(column) instanceof String) {
                    String value = (String) filterMap.get(column);
                    q.setParameter(column, value.toUpperCase() + "%");
                } else {
                    q.setParameter(column, filterMap.get(column));
                }
            }
        }
     return q;
 }
 
}


PersonDAO.java

import javax.ejb.Local;

@Local
public interface PersonDAO extends GenericDAO {

}

PersonDAOImpl.java

import javax.ejb.Stateless;

import at.ooegkk.svnet.juhealth.dao.PersonDAO;

@Stateless
public class PersonDAOImpl extends GenericDAOImpl implements PersonDAO {
    
}

AbstractEntity.java

import java.io.Serializable;

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;

@MappedSuperclass
public class AbstractEntity implements Serializable {

 /**
  * 
  */
 private static final long serialVersionUID = 1L;
 
 private Long id;


 @Id
 @GeneratedValue(strategy=GenerationType.AUTO)
 public Long getId() {
  return id;
 }

 public void setId(Long id) {
  this.id = id;
 }

}

Person.java

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.OneToOne;
import javax.persistence.Table;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.validator.Length;
import org.hibernate.validator.NotNull;
import org.jboss.seam.annotations.Name;

import at.ooegkk.intranet.example.validator.VSNR;

@Name("person")
@Entity
@Table
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class Person extends AbstractEntity implements Serializable {
 /**
  * 
  */
 private static final long serialVersionUID = 1L;
 private String vorname;
 private String nachname;
 private String vsnr;
 private Date geburtsdatum;
 private Address adresse = new Address();
 
 @Column
 @NotNull(message="#{messages['field.required']}")
 public String getVorname() {
  return vorname;
 }
 public void setVorname(String vorname) {  
  this.vorname = vorname;
 }
 @Column
 public String getNachname() {
  return nachname;
 }
 public void setNachname(String nachname) {
  this.nachname = nachname;
 }
 @Column
 @NotNull
 @Length(max=10,min=10)
 public String getVsnr() {
  return vsnr;
 }
 public void setVsnr(String vsnr) {
  this.vsnr = vsnr;
 }
 @OneToOne(targetEntity=Address.class)
 @Cascade(value=CascadeType.ALL)
 public Address getAdresse() {
  return adresse;
 }
 public void setAdresse(Address adresse) {
  this.adresse = adresse;
 }
 @Column
 public Date getGeburtsdatum() {
  return geburtsdatum;
 }
 public void setGeburtsdatum(Date geburtsdatum) {
  this.geburtsdatum = geburtsdatum;
 }
}

Address.java

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;

import org.hibernate.validator.NotNull;
import org.jboss.seam.annotations.Name;

@Entity
@Table
@Name("adresse")
public class Address extends AbstractEntity implements Serializable {
 
 /**
  * 
  */
 private static final long serialVersionUID = 1L;
 private Integer plz;
 private String ort;
 private String strasse;
 @Column
 public Integer getPlz() {
  return plz;
 }
 public void setPlz(Integer plz) {
  this.plz = plz;
 }
 @Column
 public String getOrt() {
  return ort;
 }
 public void setOrt(String ort) {
  this.ort = ort;
 }
 @Column
 @NotNull
 public String getStrasse() {
  return strasse;
 }
 public void setStrasse(String strasse) {
  this.strasse = strasse;
 }
}

index.xhtml

<rich:dataTable value="#{personPaginatingDataModel}" var="person" rows="3" reRender="scroller" id="simpletable" width="100%" rowClasses="firstrow,secondrow"<
 <f:facet name="header"<
  <h:outputText value="Personen"/<
 </f:facet<
 <rich:column width="10%"<
  <s:link action="#{personManager.personEdit}"<
   <f:param name="personId" value="#{person.id}"/<
   <img src="/img/icon_bearbeiten.gif" alt="Bearbeiten"/<
  </s:link<
 </rich:column<
 <rich:column width="18%"< 
  <f:facet name="header"<
   <h:outputText value="SVNR"/<
  </f:facet<
  <h:outputText value="#{person.vsnr}"/<
 </rich:column<
 <rich:column sortBy="#{person.vorname}" filterBy="#{person.vorname}" filterEvent="onkeyup" filterValue="#{personManager.currentVornameFilterValue}" width="18%"< 
  <f:facet name="header"<
   <h:outputText value="Vorname"/<
  </f:facet<
  <h:outputText value="#{person.vorname}"/<
 </rich:column<
 <rich:column filterBy="#{person.nachname}" filterEvent="onkeyup" filterValue="#{personManager.currentNachnameFilterValue}" width="18%"< 
  <f:facet name="header"<
   <h:outputText value="Nachname"/<
  </f:facet<
  <h:outputText value="#{person.nachname}"/<
 </rich:column<
 <rich:column width="18%"<
  <f:facet name="header"<
   <h:outputText value="Strasse"/<
  </f:facet<
  <h:outputText value="#{person.adresse.strasse}"/<
 </rich:column< 
 <rich:column width="8%"<
  <f:facet name="header"<
   <h:outputText value="PLZ"/<
  </f:facet<
  <h:outputText value="#{person.adresse.plz}"/<
 </rich:column< 
 <rich:column width="10%"<
  <f:facet name="header"<
   <h:outputText value="Ort"/<
  </f:facet<
  <h:outputText value="#{person.adresse.ort}"/<
 </rich:column<
</rich:dataTable<
<rich:datascroller id="scroller" renderIfSinglePage="false" for="simpletable"<</rich:datascroller<

Edit at 2012/09/26: The reason that some readers were asking for the source code of the PersonManager, I edited this post. Because I have improved the PersonManager since the creation of the first post, I inserted an older version from the repository. Hopefully it is compatible with the other code.

PersonManager

import java.util.List;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Begin;
import org.jboss.seam.annotations.End;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.web.RequestParameter;

import at.ooegkk.svnet.juhealth.datamodel.PersonPaginatingDataModel;
import at.ooegkk.svnet.juhealth.entity.Person;
import at.ooegkk.svnet.juhealth.service.PersonService;

@Name("personManager")
@Scope(ScopeType.SESSION)
public class PersonManager {

    @In(required = false)
    private PersonService personService;

    @In(create = true)
    @Out(required = false)
    public Person person;

    @Out(required = false)
    public Person searchPerson = new Person();

    @RequestParameter
    public Long personId;

    public String currentVornameFilterValue;

    public String currentNachnameFilterValue;

    @In(create = true)
    public PersonPaginatingDataModel personPaginatingDataModel;

    public PersonManager() {
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    public Person getSearchPerson() {
        return searchPerson;
    }

    public void setSearchPerson(Person searchPerson) {
        this.searchPerson = searchPerson;
    }

    public PersonPaginatingDataModel getPersonPaginatingDataModel() {
        return personPaginatingDataModel;
    }

    public void setPersonPaginatingDataModel(PersonPaginatingDataModel personPaginatingDataModel) {
        this.personPaginatingDataModel = personPaginatingDataModel;
    }

    public String personenList() {
        return "personenList";
    }

    public String personAdd() {
        return "personAdd";
    }

    @Begin(join = true)
    @End
    public String personSave() throws Exception {
        try {
            if (person.getId() == null) {
                this.personService.save(person);
            } else {
                this.personService.merge(person);
            }
            return "personSave";
        } catch (Exception e) {
            throw e;
        }
    }

    public String personReset() throws Exception {
        try {
            person = new Person();
            return "personReset";
        } catch (Exception e) {
            throw e;
        }
    }

    public String personEdit() throws Exception {
        try {
            person = this.personService.getPersonById(personId);
            return "personEdit";
        } catch (Exception e) {
            throw e;
        }
    }

    public String personenSearch() throws Exception {
        try {
            return "personenSearch";
        } catch (Exception e) {
            throw e;
        }
    }

    public List getPersonen() throws Exception {
        try {
            return this.personService.findAllPersons();
        } catch (Exception e) {
            throw e;
        }
    }

    public String getCurrentVornameFilterValue() {
        return currentVornameFilterValue;
    }

    public void setCurrentVornameFilterValue(String currentVornameFilterValue) {
        this.currentVornameFilterValue = currentVornameFilterValue;
    }

    public String getCurrentNachnameFilterValue() {
        return currentNachnameFilterValue;
    }

    public void setCurrentNachnameFilterValue(String currentNachnameFilterValue) {
        this.currentNachnameFilterValue = currentNachnameFilterValue;
    }

}