From: Lew on
carmelo wrote:
> Thank you guys for your answers.
>
> < how slow is slow? Roughly how many seconds for what size result
> sets?
> 59 seconds for 439 records!

I load a 3,000-entry List from JPA over a relatively slow network in one
project virtually instantly. It's not JPA that is at fault.

I don't bother with pagination when I'm using JPA. It ends up defeating the
internal caching and available external caching capabilities of the JPA
provider. It also messes up your architecture.

When you use JPA, you need to avoid thinking of your persistent information as
"data". The whole point of JPA is to give an *object-oriented* view of
persistent entities and their relationships. The fact that you are tangled in
questions of "performance" (and trying to blame JPA for it) and "batches"
indicates that you are not working from an object-oriented view of your
architecture. That is the real mistake.

You get the best performance out of JPA when you use it in its natural mode.
Then it's quite fast, at least as much so as normal database access will let
it be. Done properly, it'll never be "JPA" that is to blame for performance
issues.

You have given us absolutely no information about the data or object models
you use, or how you try to integrate them. Then you expect miracles from
Usenet to solve your problem, while you go haring off after chimeras like "JPA
hurts performance". It doesn't work that way.

Well, I guess it does, now that you are reading this post.

JPA has built-in support for certain natural kinds of "batches", that is,
one-to-many, many-to-one and many-to-many relationships of entities or sets of
entities to each other. It fills these batches quite efficiently
appropriately to the programmer's choice of 'Fetch.EAGER' or 'Fetch.LAZY'.
The former uses JOIN syntax (assuming proper integrity constraints in the
database) and the latter conditional queries.

With only 439 entities ("records" is a data-oriented term) you don't need to
page. That's just silly.

If you keep entity size reasonable, let's say about a KiB apiece, you can hold
a thousand per MiB. In batch sizes over about ten thousand it starts to
become useful to think about paging.

Proper data models on the database side are an absolute must. Screw up the
data model and too bad for you.

JPA works best for simpler data models, ones that have clean representations
of entities. It can handle beautifully any relationships expressible in
collection terms, and since relational databases are set oriented by
definition, that is anything that a relational database expresses.

As relationship complexity grows, particularly with many-to-many links, the
@JoinX() annotations become mandatory. You have to get pretty specific in
your mappings, telling JPA not only the fields through which you link but the
table and column names involved.

The Java EE 5 tutorial on java.sun.com goes through some of this, and googling
around will find you more examples. I find the documentation for each of
EclipseLink, Hibernate and Apache OpenJPA all to be very useful, regardless of
which one you actually happen to use.

--
Lew
From: carmelo on
Thank you Lew for your exaustive answer, but I've got this performance
problem and I need to solve it. I hope you can help me finding a
reasonable solution. I'm happy that you can load thousands of records
with JPA, so I hope I can load them so fast too.. However, locally, in
my local area network, I can load thousands of records with JPA too..
the performance problem begins when I try to load data from a remote
host database.

To be clear, here is my JPA query and my entity code:

//query
String query = "SELECT a FROM Articoli a";
Query q = entityManager.createQuery(query);
java.util.Collection data = q.getResultList();
for (Object entity : data) {
entityManager.refresh(entity);
}
list.clear();
list.addAll(data);

//entity, Articoli.java
@Entity
@Table(name = "articoli")
@NamedQueries({
..here are NamedQueries for each field..
})
public class Articoli implements Serializable {
@Transient
private PropertyChangeSupport changeSupport = new
PropertyChangeSupport(this);
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "CodiceArticolo")
private Integer codiceArticolo;
@Basic(optional = false)
@Column(name = "Descrizione")
private String descrizione;
@Column(name = "H")
private BigDecimal h;
@Column(name = "L")
private BigDecimal l;
@Column(name = "Diametro")
private BigDecimal diametro;
@Column(name = "Spessore")
private BigDecimal spessore;
@Basic(optional = false)
@Column(name = "prezzo")
private BigDecimal prezzo;
@Column(name = "Lungh")
private BigDecimal lungh;
@Column(name = "Peso")
private BigDecimal peso;
@Column(name = "Lung_scatola")
private BigDecimal lungscatola;
@Column(name = "Larg_scatola")
private BigDecimal largscatola;
@Column(name = "H_scatola")
private BigDecimal hscatola;
@Column(name = "Peso_scatola")
private BigDecimal pesoscatola;
@Column(name = "Spessore_max")
private BigDecimal spessoremax;
@Column(name = "Lampada")
private String lampada;
@Column(name = "Watt")
private BigDecimal watt;
@Column(name = "Lumen")
private BigDecimal lumen;
@Column(name = "Temp_colore")
private BigDecimal tempcolore;
@Column(name = "Trattamento")
private String trattamento;
@Column(name = "T_50_min")
private BigDecimal t50min;
@Column(name = "T_50_max")
private BigDecimal t50max;
@Column(name = "Id")
private BigDecimal id;
@Column(name = "Od")
private BigDecimal od;
@Version
protected int version;
@JoinColumn(name = "CodiceSezione", referencedColumnName =
"codice")
@ManyToOne
private Sezione codiceSezione;
@JoinColumn(name = "CodiceCategoria1", referencedColumnName =
"codice")
@ManyToOne
private Categoria codiceCategoria1;
@JoinColumn(name = "CodiceCategoria2", referencedColumnName =
"codice")
@ManyToOne
private Categoria codiceCategoria2;
@JoinColumn(name = "CodiceCategoria3", referencedColumnName =
"codice")
@ManyToOne
private Categoria codiceCategoria3;
@JoinColumn(name = "CodiceCategoria4", referencedColumnName =
"codice")
@ManyToOne
private Categoria codiceCategoria4;
@OneToMany(cascade = CascadeType.ALL, mappedBy =
"codicearticolomagaz")
private Collection<Magazzinodettaglio>
magazzinodettaglioCollection;
@OneToMany(cascade = CascadeType.ALL, mappedBy =
"codicearticolofornitore")
private Collection<Articolifornitori> articolifornitoriCollection;

public Articoli() {
}

public Articoli(Integer codiceArticolo) {
this.codiceArticolo = codiceArticolo;
}

public Articoli(Integer codiceArticolo, String descrizione,
BigDecimal prezzo) {
this.codiceArticolo = codiceArticolo;
this.descrizione = descrizione;
this.prezzo = prezzo;
}

..here are getter/setter methods for each field..

public Collection<Magazzinodettaglio>
getMagazzinodettaglioCollection() {
return magazzinodettaglioCollection;
}

public void
setMagazzinodettaglioCollection(Collection<Magazzinodettaglio>
magazzinodettaglioCollection) {
this.magazzinodettaglioCollection =
magazzinodettaglioCollection;
}

public Collection<Articolifornitori>
getArticolifornitoriCollection() {
return articolifornitoriCollection;
}

public void
setArticolifornitoriCollection(Collection<Articolifornitori>
articolifornitoriCollection) {
this.articolifornitoriCollection =
articolifornitoriCollection;
}


@Override
public int hashCode() {
int hash = 0;
hash += (codiceArticolo != null ? codiceArticolo.hashCode() :
0);
return hash;
}

@Override
public boolean equals(Object object) {
if (!(object instanceof Articoli)) {
return false;
}
Articoli other = (Articoli) object;
if ((this.codiceArticolo == null && other.codiceArticolo !=
null) || (this.codiceArticolo != null && !
this.codiceArticolo.equals(other.codiceArticolo))) {
return false;
}
return true;
}

@Override
public String toString() {
return "desktopapplication1.Articoli[codiceArticolo=" +
codiceArticolo + "]";
}

public void addPropertyChangeListener(PropertyChangeListener
listener) {
changeSupport.addPropertyChangeListener(listener);
}

public void removePropertyChangeListener(PropertyChangeListener
listener) {
changeSupport.removePropertyChangeListener(listener);
}

}


If it's not enough, please let me know, and I'll prepare a sample
application.

I hope you can help me to achive a better performance.

Thank you very much in advance for your help guys!
From: Alessio Stalla on
On Apr 19, 9:21 am, carmelo <csa...(a)tiscali.it> wrote:
> To be clear, here is my JPA query and my entity code:
>
> //query
> String query = "SELECT a FROM Articoli a";
> Query q = entityManager.createQuery(query);
> java.util.Collection data = q.getResultList();
> for (Object entity : data) {
>    entityManager.refresh(entity);}

Are you aware of the fact that refresh() will reload the entity from
the database, therefore issuing a query? And that many-to-one
relations are eager by default, so for each entity you'll be fetching
all the related objects each time?

hth,
Alessio
From: carmelo on
Thank you Alessio for your reply.

> Are you aware of the fact that refresh() will reload the entity from the database, therefore issuing a query?

You're right! refresh() issues a new query. I removed it, but the
loading time is now about 49 seconds, still too high. Even if it's
better that 59 seconds :-)

> many-to-one relations are eager by default, so for each entity you'll be fetching all the related objects each time?

How can I do? There are n:1 relations between Sezione, Categoria and
Articoli. In other words, Articoli.sezione can have multiple values,
which values are taken from Sezione. And the same is for
Articoli.categoria1, Articoli.categoria2, Articoli.categoria3,
Articoli.categoria, which values are taken from Categoria.
However I tried to remove these relations, and the resulting loading
time was 46 secods, therefore I saved only 3 seconds.


I hope you can help me achiving better results. Thanks guys
From: Martin Gregorie on
On Mon, 19 Apr 2010 00:21:47 -0700, carmelo wrote:

> Thank you Lew for your exaustive answer, but I've got this performance
> problem and I need to solve it. I hope you can help me finding a
> reasonable solution. I'm happy that you can load thousands of records
> with JPA, so I hope I can load them so fast too.. However, locally, in
> my local area network, I can load thousands of records with JPA too..
> the performance problem begins when I try to load data from a remote
> host database.
>
I think your best bet is to do some monitoring to see exactly what is
going on at the network.

Start by instrumenting your code so you can see how big the retrieved
dataset has to be to cause problems.

Then install a copy of Wireshark (available for most OS) and use it to
inspect the network traffic when your query is running. Look at smaller
queries as well as ones big enough to cause the problem to pick up any
significant differences, e.g. does the nature of the traffic change as
the retrieved data expands from a few display lines to more than a
screenful? Does scrolling to the next page cause network activity?

Turning on JDBC and/or DBMS tracing can help too.

You're probably going to have to wade through great heaps of captured
data, especially if the problem only occurs with large data sets, but the
GUI used by later copies of Wireshark can help there.

I've seen awfully dumb tricks played by both software and database
designers that have only come to light when large data volumes and/or
slower data links were used, e.g.:

- really stupid auto-generated SQL
(PowerBuilder - fortunately it would also accept manually written SQL)

- dumb developer tricks
(Microsoft Foundation Classes - MFC queries managed to scan the whole
table when getting the column names for 'SELECT * FROM...' queries)

- poor DB schema design
( missing indexes aren't apparent with typical programmer small test
data sets but boy do they bite once row counts are in the ten
thousands)


--
martin@ | Martin Gregorie
gregorie. | Essex, UK
org |