|
|
|||||||||
Wonseok Kim's BlogJ2EE ArchivesJava EE 6: module and application scopes of java:comp/env?Posted by guruwons on June 05, 2008 at 08:47 PM | Permalink | Comments (3)I would like to get feedbacks about
application-scoped and module-scoped resource/EJB references (in
As you know, when we define simple
environment entries or refer to data sources or EJBs we need to
specify
<env-entry>
<env-entry-name>foo/maximum</env-entry-name>
<env-entry-type>java.lang.Integer</env-entry-type>
<env-entry-value>10</env-entry-value>
</env-entry>
<resource-ref>
<res-ref-name>myDataSource</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
</resource-ref>
<ejb-ref>
<ejb-ref-name>ejb/myBean</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<remote>some.package.MyBeanInterface</remote>
</ejb-ref>
or via annotations on the component class
Then we use these Current limitation is that EJB module can only have component-scoped(per-bean) namespace so that you need to define the same references and mappings again and again for each EJB. This is different from web module which has only module scope. Therefore it's hard to define shared configuration/references at module level and it's especially inconvenient for module default interceptors. Also there is no notion of application scope for sharing between modules inside one application. So it's difficult to share those between modules. As we try to enable packaging EJBs in web module, it's also an option to add component scope to web module so each EJB has its own scope. I'm not sure that we need to add this for servlet components either. Even though there are multiple scopes,
you don't need to find out exactly where the entries are defined.
General scope rule will be applied here. When you looking up with
I would like to hear from you about this.
Please give us any use cases or reasons if you have. Your feedbacks are important to add these extensions to Java EE 6. TopLink Essentials enhancements - category-specific logging level and cascade refresh query hintPosted by guruwons on July 06, 2007 at 07:39 PM | Permalink | Comments (4)Recently I've implemented two minor but useful features for TopLink Essentials(TLE) v2 b54(or GlassFish v2 b54). One is the category-specific logging level configuration[1] which is useful for who want to see only SQL logs and the other is the cascade refresh query hint[2] which control the cascade scope of the refresh query dynamically. Let's see what these are... Category-specific logging levelConfiguring logging level for a PU(Persistence Unit) could be done by "toplink.logging.level" property like below.<persistence-unit name="pu1"> To see SQLs triggerred by TLE the logging level should be FINE. However this will print other many FINE logs which may be of little interest to normal users. This is like all-or-nothing choice. Fortunately the logging platform of TLE basically supports categories for different types of logs - "sql", "connection", "transaction" and so on. This enable users to watch interested logs of specific categories. Especially the "sql" category is the most interesting one. However there had been no easy way to configure the logging level for the specific category. So I've added the following new category logging level property and connected it with logger code.
Cascade refresh query hint for refresh queryTLE provides the query hint toplink.refresh to fetch up-to-date entities from database like below. Query query = em.createQuery("SELECT e FROM Employee e WHERE e.id = :id");The associated entities will be refreshed also if they are specified with cascade=REFRESH. However the cascade metadata is static configuration which can not be changed in runtime and there may be some cases to apply different cascade options - do not cascade or cascade to all associations from time to time regardless of predefined cascade option. That is, more dynamic cascade refresh option can be useful. Hence I added the following query hint.
At this time CascadePrivateParts option has no difference from CascadeAllParts for Java Persistence Entity, but when using underlying TopLink feature this could behave different in the future. Other existing properties and query hints are documented in http://www.oracle.com/technology/products/ias/toplink/JPA/essentials/toplink-jpa-extensions.html Have fun with this enhancements and please comment how you feel. [1] GlassFish Issue#1338 [2] GlassFish Issue#3269 TopLink Essentials 2.0 and maven repositoryPosted by guruwons on February 17, 2007 at 02:37 AM | Permalink | Comments (4)TopLink Essentials 2.0?TopLink Essentials(GlassFish JPA implementation) now come to have a new version 2.0. But, don't be surprised, this is just versioning change from 9.1. TopLink Essentials 2.0 and 9.1 are the same version. Originally TopLink Essentials had a version like 9.0(GlassFish v1), 9.1(GlassFish v2) after the version of Sun Java System Application Server. Since TopLink Essentials is an independent framework which can be used without GlassFish(SJSAS) container, it doesn't need to be tied to SJSAS version. Also it could be top-level project later, TopLink Essentials team decided to use new versioing policy. This versioning change is applied to the recent build (b36) in GlassFish v2[1], that is 2.0-b36. Maven repositoryNow you can download the latest binaries and sources in the java.net maven repository! https://maven-repository.dev.java.net/nonav/repository/toplink.essentials/ The POM will look like below: <project>
<modelVersion>4.0.0</modelVersion>
<groupId>toplink.essentials</groupId>
<artifactId>toplink-essentials</artifactId>
<version>2.0-36</version>
<dependencies>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0b</version>
</dependency>
</dependencies>
</project>Download it manually or use maven dependency mechanism with above groupId and artifactId. As you can infer, the latter version fragment (after dash) is build number (e.g. 36 in above). This build number is same as GlassFish build number. From now, every builds after b36 will be published to the repository. To check version statically, see META-INF/MANIFEST.MF inside toplink-essentials.jar file and you will see like below. Implementation-Version: 2.0-36 (02/17/2007) To check version in runtime, find the log printed by TopLink Essentials like below. [TopLink Info]: ...TopLink, version: Oracle TopLink Essentials - 2.0 (Build 36 (02/17/2007)) With this versioning change TopLink Essentials in GlassFish v1(SJSAS 9.0) might be called as TopLink Essentials 1.0. toplink-essentials-agent.jar is deprecatedThere is also a notable enhancement[2] after 2.0-b36. toplink-essentials-agent.jar file is no longer needed. There were two jars files - toplink-essentials.jar which contains JPA API classes and implementations, and toplink-essentials-agent.jar which contains javaagent. The agent jar had been used for users who want to use lazy-fetching feature in Java SE mode[3]. Now the agent facility is integrated into toplink-essentials.jar. So you can do like below. $ java -javaagent:toplink-essentials.jar client.Client For existing users, toplink-essentials-agent.jar will be provided continuously for the time being. It's really convenient that we just need to carry one jar file toplink-essentials.jar for JPA programming. Hope you enjoy TopLink Essentials 2.0! [1] https://glassfish.dev.java.net/issues/show_bug.cgi?id=2405 [2] https://glassfish.dev.java.net/issues/show_bug.cgi?id=2409 [3] The innermost secrets of -javaagent:toplink-essentials-agent.jar revealed Sending large files over JMSPosted by guruwons on November 15, 2006 at 08:30 AM | Permalink | Comments (10)JMS provides powerful asynchronous communication, but it is message-oriented and doesn't support InputStream/OutputStream at all. So it is nearly impossible to send a large file without loading it on memory. How can we acheive this? Let me talk an enhancement of JMS for this issue. ActiveMQ provides InputStream/OutputStream extension on JMS Connection. And MantaRay JMS also supports similar InputStream/OutputStream facilities over JMS destinations. But these are not so good because they don't support JMS Session (this means they don't support transacted session either) and JMS Message in this case. It also looks awkward because it is far from message-based JMS API. It appears to me that it is impossible to handle both normal JMS messages and file on the same destination. There is a better idea on JBoss forum. It is mentioning a FileMessage like below. Interesting. In order to avoid storing the payload of the message in memory at any time, we could create a new type of message e.g. "FileMessage" which just contains a pointer to the file on disc. When the message is sent from client->server or from server node-> server node, the file is streamed rather than serialized, or the pointer is just passed if the filesystem is visible from both source and destination. Perhaps we could extend this technique to any large message. - Tim Let me go further. FileMessage contains the URL of the file to send like below. FileMessage
public interface FileMessage extends javax.jms.Message {
/**
* Returns the URL of the file.
* The URL object is readable by <code>URL.openStream()</code>.
*
* @return the <code>URL</code> of the file.
*/
URL getURL();
/**
* Set the URL of the file.
* The URL object should be readable by <code>URL.openStream()</code>.
*
* @param url the <code>URL</code> of the file.
* @exception MessageNotWriteableException if the message is in read-only mode.
*/
void setURL(URL url) throws MessageNotWriteableException;
}
ShowcaseLet's see how we can send any file using above FileMessage.
InitialContext ic = new InitialContext();
QueueConnectionFactory qconFactory = (QueueConnectionFactory)ic.lookup("QueueConnectionFactory");
QueueConnection qcon = qconFactory.createQueueConnection();
// JEUS-specific Session
JeusQueueSession qsession = (JeusQueueSession) qcon.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = (Queue)ic.lookup("ExamplesQueue");
QueueSender qsender = qsession.createSender(queue);
// Create a FileMessage using JeusQueueSession
FileMessage msg = qsession.createFileMessage();
// set file URL and send the message
File file1 = new File("/home/wonskim/temp/file1.jar");
msg.setURL(file1.toURL());
qsender.send(msg);
qsender.close();
qsession.close();
qcon.close();
Senders just create a FileMessage and set the URL of the file. JMS runtime will read the content of the file using Let's see receivers side. Receivers can be MDB or normal JMS Listener.
public void onMessage(Message msg) {
if (msg instanceof FileMessage) {
URL url = ((FileMessage)msg).getURL();
if(url != null){
// read the content of the file
InputStream inputStream = url.openStream();
// do whatever you want...
}
}
}
Receivers receive a FileMessage like other normal JMS messages. The URL value of the file is not same as what senders set. Receiver clients can just use the URL to get the content of the file like Two StrategiesActually there are two strategies in JMS runtime.
I think the second is better because the content of the file is transferred only if it is required. There is one thing to consider - The remote URL will be valid only before the ack of the message is sent back to JMS Server. Because JMS Server can remove the file after the delivery of messages is confirmed. With AUTO acknowlege mode the ack will be sent after I've tried to implement this new type of message on TmaxAS JEUS(My company's Application Server). The result has been successful. What do you think of this idea?
JPA: Supplement to the access type of @Embeddable classPosted by guruwons on October 16, 2006 at 08:11 AM | Permalink | Comments (1)In Java Persistence API, Embeddable class is used to represent composite primary key or share common columns between entities. But when sharing Embeddable classes, you should be cautious whether its fields or properties are mapped to the database due to the unclear spec. I will introduce the supplementary rules to the spec which is implemented in GlassFish(TopLink Essentials). First, guess what will happen if a Embeddable class is used as below? @Embeddable public class E {
// has no metadata. It has two fields, but one property!
int i;
int j;
int getI() { return i; }
void setI(int i) { this.i = i; }
}
@Entity public class A {
@Id String id; // entity class uses FIELD access
@Embedded E e;
}
@Entity public class B {
String id;
E e;
@Id String getId() { return id; } // entity class uses PROPERTY access
void setId(String id) { this.id = id; }
@Embedded E getE() { return e; }
void setE(E e) { this.e = e; }
}
And what will happen if the field E.i has @Basic annotation? This situations can be confused and Java Persistence spec is not so clear about this. Sanjeeb Kumar Sahoo nicely addressed this issue[1] and concluded with supplementary rules. These rules are confirmed by spec leaders and implemented in RI GlassFish. The text here is written by Sahoo. I just added some examples. Unclear specificationThe spec seems to be not clear on this subject as the following sections show: 2.1.5 Embeddable Classes ... Embeddable classes must adhere to the requirements specified in Section 2.1 for entities with the exception that embeddable classes are not annotated as Entity. Embeddable classes must be annotated as Embeddable or denoted in the XML descriptor as such. The access type for an embedded object is determined by the access type of the entity in which it is embedded. Support for only one level of embedding is required by this specification. Note, it says is determined by as opposed to can be determined. Yet, orm_1_0.xsd allows an access-type to be specified for an embeddable, there by contradicting the previous rule. Moreover, only non-portable applications can have different access-type for an embeddable as the spec says in the following section: 10.1.5.2 access The access attribute defines the access type for the embeddable class. The access attribute overrides any access type specified by the persistence-unit-defaults element or entity-mappings element for the given embeddable class. Portable applications must not specify the access attribute if mapping annotations have been applied to the fields or properties of the embeddable class or the entity with which it is associated and the value differs from the access type defined by means of annotations. Supplementary rulesHere are the rules governing access-type of an embeddable class that we have agreed on: Rule 1: In the absence of metadata in embeddable class, access-type of an embeddable is determined by the access-type of the enclosing entity. Rule 2: In the presence of metadata in embeddable class, access-type of an embeddable is determined using that metadata. This allows sharing of the embeddable in entities with conflicting access-types. Rule 3: It is an error to use a metadata-less embeddable class in entities with conflicting access-types as that might result in different database mapping for the same embeddable class. Rule 4: It is an error if metadata-complete == false, and metadata is present both in annotations and XML, and access-type as determined by each of them is not same. Rule 5: It is an error if both fields and properties of an embeddable class are annotated and metadata-complete == false. These rules are different from what is stated in the spec right now, but that's OK as the spec leads have acknoledged that the spec is not clear because some very important lines accidentally did not go into the spec. They will be clarified in a future version of the JPA spec. ExamplesRule 1 @Entity public class A {
@Id String id; // entity class uses FIELD access
@Embedded E e;
}
// FIELD access according to Rule 1
@Embeddable public class E {
int i;
int j;
int getI() { return i; }
void setI(int i) { this.i = i; }
int getJ() { return j; }
void setJ(int j) { this.j = j; }
}
Rule 2: // PROPERTY access regardless of embedding entities according to Rule 2
@Embeddable public class E {
int i;
int j;
@Basic
int getI() { return i; }
void setI(int i) { this.i = i; }
int getJ() { return j; }
void setJ(int j) { this.j = j; }
}
@Entity public class A {
@Id String id; // entity class uses FIELD access
@Embedded E e; // E uses PROPERTY access
}
@Entity public class B {
String id;
E e;
@Id String getId() { return id; } // entity class uses PROPERTY access
void setId(String id) { this.id = id; }
@Embedded E getE() { return e; } // E uses PROPERTY access
void setE(E e) { this.e = e; }
}
Rule 3: The following case is illegal: @Embeddable public class E {
// has no metadata. It has two fields, but one property!
int i;
int j;
int getI() { return i; }
void setI(int i) { this.i = i; }
}
@Entity public class A {
@Id String id; // entity class uses FIELD access
@Embedded E e;
}
@Entity public class B {
String id;
E e;
@Id String getId() { return id; } // entity class uses PROPERTY access
void setId(String id) { this.id = id; }
@Embedded E getE() { return e; }
void setE(E e) { this.e = e; }
}
If we allow this use case, then Class C will have two different persistence views in the same PU. From A's point of view, it has two persistence fields. From B's point of view, it has one persistence property. This kind of sharing should be disallowed. User could have annotated either a field or property of C and that would have not allowed sharing of the embeddable in A & B, but it would have provided a unique view of C to both the enclosing entity classes. Rule 4: The following case is illegal: @Embeddable public class E {
@Basic
int i;
int j;
int getI() { return i; }
void setI(int i) { this.i = i; }
int getJ() { return j; }
void setJ(int j) { this.j = j; }
}
<embeddable class="E" access="PROPERTY">
<attributes>
...
</attributes>
</embeddable>
Rule 5: The following case is illegal: @Embeddable public class E {
@Basic
int i;
int j;
@Basic
int getI() { return i; }
void setI(int i) { this.i = i; }
int getJ() { return j; }
void setJ(int j) { this.j = j; }
}
References[1] https://glassfish.dev.java.net/issues/show_bug.cgi?id=831
How to configure a logger type in TopLink Essentials (Part 2)Posted by guruwons on September 22, 2006 at 01:29 AM | Permalink | Comments (2)Previously I bloged about How to use Java Logger in Java SE mode - a workaround to change logger type. But the workaround is no more needed. GlassFish v2(from b17) implemented a new feature - the configuration for logger type for this. It's very easy to use Java logger in Java SE mode, just add following property to persistence.xml. <property name="toplink.logging.logger" value="JavaLogger"/> Or you can do it programatically like below. Map properties = new HashMap();
properties.add("toplink.logging.logger", "JavaLogger");
...
EntityManagerFactory emf = Persistence.createEntityManagerFactory("your-unit-name", properties);
Valid values are one of the following or custom class name which implements oracle.toplink.essentials.logging.SessionLog. * DefaultLogger - internal default logger(Standard out). In Java EE environment, this property is normally not required because AppServers such as GlassFish is already using JavaLogger by implciticly adding ServerPlatform("toplink.target-server" or "toplink.server.platform.class.name") property. But if you specify "toplink.logging.logger" property then it will override the ServerPlatform's logger configuration. Implementing a new logger type or extending existing logger Currently there are just two logger types. To implement acustom logger make a logger class by implementing oracle.toplink.essentials.logging.SessionLog or extending oracle.toplink.essentials.logging.AbstractSessionLog. And put the class name on the logger type property. <property name="toplink.logging.logger" value="your-logger-class-name"/> See oracle.toplink.essentials.logging.JavaLog source for reference. If you want a special kind of logger, consider extending existing logger. You may be able to implement a logger which handles SQL log specially - see SessionLog's SQL category. Make your own logger and if it is useful for others, please contribute by sending it to persistence@glassfish.dev.java.net !
Understanding the cache of TopLink Essentials(GlassFish JPA)Posted by guruwons on September 07, 2006 at 08:38 PM | Permalink | Comments (26)IntroductionThe hype of JPA has slowed down and time has come to apply this to real applications. To use JPA properly knowing JPA spec is not enough because JPA spec doesn't cover all aspects and the behaviour can be a little bit different in persistence providers. Especially 2nd-level cache is the one. Which is not covered by JPA spec, but most providers provides it. To increase performance and get expected results, you should understand the cache. I will talk about the cache extension of TopLink Essentials(GlassFish JPA RI), and I think this is essential part to use JPA properly in GlassFish. This article is based on GlassFish V2 b15. Persistence Context(If you know Persistence Context well, you can skip this section.) Persistence Context is a key concept in JPA. Persistence context is similar to first-level cache. To be more accurate persistence context is not cache but the working set of managed entities. Which is synchronized to database when flushing or committing. (I will not explain persistence context in detail here, if you need you should refer to other materials.) Entities in the persistence context are never evicted unless persistence context is cleared explicitly or implicitly(EntityManager is closed when tx complete). Also the persistence context is not refreshed unless you explicitly invoke EntityManager.refresh() method. Persistence context maintains one entity instance for one persistence identity(primary key) like below. // in the same persistence context
Employee e1 = em.find(Employee.class, 100);
...
Employee e2 = em.find(Employee.class, 100);
...
Employee e3 = (Employee)
em.createQuery("SELECT e FROM Employee e WHERE e.id = :id")
.setParameter("id", 100)
.getSingleResult();
// e1 == e2 && e2 == e3
When using container-managed transaction-scoped EntityManager, the scope of persistence context is a transaction. So this first-level cache is used in transaction scope. It's not enough to only understand persistence context. You need to know also vendor-specific 2nd-level cache as well. Let's continue... Session CacheTopLink Essentials provides 2nd-level cache called Session Cache. Session cache is maintained in internal server session. And one internal server session is created per a persistence unit. The session cache is shared over all the clients. See the following picture.
In JPA client session corresponds to EntityManager(persistence context). So all EntityManagers from same persistence unit shares the session cache. Will be session cache shared between applications? Nop. Persistence unit is maintained per Java EE application. So the scope of session cache is one Java EE application. It's not shared between applications. Also TopLink Essentials doesn't provide distributed cache so it can not be shared between clustered applications over several appserver instance. Session cache is turned on by default so you can use it now without any extra configuration. With this 2nd-level cache you can get performance benefits. When you get entities which are not in the persistence context TopLink uses cached entities in the session cache(clones of cache entites are returned). But EntityManager.find() and a SELECT query behave differently. EntityManager.find() checks session cache first before goes to database, but a SELECT query doesn't check cache first and always goes to database. Although a query goes to database, it avoids rebuilding an entity and the entity in the session cache is reused(some performance gain). Normally when a transaction is completed a persistence context(or a EntityManager) is closed and new persistence context is used in another transaction. Session cache is useful in this situation. For example, //EntityManager is created and closed for a transaction
Long id = 100L;
//FIRST TRANSACTION
begin();//create a new em. start a transaction.
Employee e1 = em.find(Employee.class, id);
commitAndClose();//commit the transaction. close em.
//SECOND TRANSACTION
begin();
Employee e2 = em.find(Employee.class, id);
commitAndClose();
//THIRD TRANSACTION
begin();
Employee e3 = (Employee)
em.createQuery("SELECT e FROM Employee e WHERE e.id = :id")
.setParameter("id", id)
.getSingleResult();
commitAndClose();
If Employee#100 was never retrived from database before the FIRST transaction, FIRST em.find() goes to database, build an entity and stores it on the session cache. SECOND em.find() gets a cache hit so don't goes to database. The THIRD query goes to database always but doesn't rebuild an entity and the entity in session cache is used. If there is modifications/deletions of entities in the persistence context they are synchronized to session cache after a transaction committed, so the state of session cache is updated. Cache Options There are several properties related to session cache.
(xxx is entity name or "default") These properties are well explained in the TopLink JPA Extensions for Caching. Check it out! If there are external changes...?It will be no problem if the application is only one which modifies the database. But if there is a external change to the database (i.e. by an other application, by SQL/JDBC or by manual), session cache will be out of date. Whenever you read entities by EntityManager.find() or JPQL query, your application may get out-dated entities. Let’s see the following example. //EntityManager is created and closed for a transaction
Long id = 100L;
//FIRST TRANSACTION
begin();//create a new em. start a transaction.
Employee e1 = em.find(Employee.class, id);
println("address1 = " + e1.getAddress());
commitAndClose();//commit the transaction. close em.
//LET'S UPDATE THROUGH JDBC (EXTERNAL CHANGE!)
Statement stmt = connection.createStatement();
stmt.executeUpdate("UPDATE EMP SET address = 'New' WHERE EMPID = " + id);
stmt.close();
//SECOND TRANSACTION
begin();
Employee e2 = em.find(Employee.class, id);
println("address2 = " + e2.getAddress());
commitAndClose();
//THIRD TRANSACTION
begin();
Employee e3 = (Employee)
em.createQuery("SELECT e FROM Employee e WHERE e.id = :id")
.setParameter("id", id)
.getSingleResult();
println("address3 = " + e3.getAddress());
commitAndClose();
There are three transactions and I modified the address of Employee#100 between FIRST and SECOND transaction through JDBC. Session cache doesn’t know this kind of external change, so result is like below. ### address1 = Old ### address2 = Old ### address3 = Old In the THIRD transaction the query actually goes to database (invoke SELECT statement), but current mechanism does not update the session cache even in this case and the out-dated entity from cache is returned. So you should know that even the first time retrieval in the transaction may not be fresh one from database. Is this what you expect? If not, how to avoid this? There are several ways to solve this situation. I will explain them in the following sections. Getting fresh results from databaseTo get fresh data from database there are several ways. First way is using refresh operations. Other ways are explained in the following sections. There are two kinds of refresh operations – portable EntityManager.refresh() and TopLink-specific refresh hint. 1. EntityManager.refresh() Use find and refresh combination like below. This is a simple and portable way to get fresh data. Employee e = em.find(Employee.class, id);
try {
em.refresh(e);
} catch(EntityNotFoundException ex){
e = null;
}
EntityNotFoundException should be caught around refresh() because the entity may be removed externally. This way has some issues. refresh() requires a transaction (in case of container-managed EntityManager) so you need to start a transaction even if you are just doing read-only operations. Another issue is it may invoke two SELECT statements in find() and refresh() if there is no such entity in the session cache. If find() get fresh one from database refresh() is redundant operation, but there is no way to determine that find() returns fresh result, so you have to do refresh() anyway. But this case will happen just first time. 2. TopLink-specific refresh hint TopLink provides query hint "toplink.refresh", use query like below. try {
e = (Employee)em.createQuery("SELECT e FROM Employee e WHERE e.id = :id")
.setHint("toplink.refresh", "true")
.setParameter("id", id)
.getSingleResult();
} catch (NoResultException ex) {
e = null;
}
NoResultException should be caught because Query.getSingleResult() can throw it if there is no such entity. If portability doesn't matter, this is better way than the find and refresh combination because it doesn't require a transaction and it will trigger just one SELECT statement. Also this way can be used to retrieve group of entities like below. List list = em.createQuery("SELECT e FROM Employee e WHERE e.name = :name")
.setHint("toplink.refresh", "true")
.setParameter("name", name)
.getResultList();
To be more convenient you'd better make this kind of query as named query and compose a utility method like below. @NamedQuery(name="Employee.freshFindById",
query="SELECT e FROM Employee e WHERE e.id = :id",
hints=@QueryHint(name="toplink.refresh", value="true"))
public class Employee {
...
public Employee freshFind(EntityManager em, Integer primaryKey){
Employee e;
try {
e = (Employee)em.createNamedQuery("Employee.freshFindById")
.setParameter("id", primaryKey)
.getSingleResult();
} catch (NoResultException ex) {
e = null;
}
return e;
}
I placed the freshFind() utility method in the entity class, but it can be placed in other class as you wish. Pessimistic lockingPessimistic locking is another way to gurantee that loaded entities are fresh ones. This is more powerful because it also lock rows until transaction complete, so prevent another transaction from using the entities. Pessimistic locking is not JPA standard, but TopLink provides this feature through a query hint "toplink.pessimistic-lock" like below. e = (Employee)em.createQuery("SELECT e FROM Employee e WHERE e.id = :id")
.setHint("toplink.pessimistic-lock", "Lock")
.setParameter("id", primaryKey)
.getSingleResult();
Value "Lock" issues "SELECT ... FOR UPDATE" and "LockNoWait" issues "SELECT ... FOR UPDATE NO WAIT". If you turn on pessimistic locking it automatically turn on "toplink.refresh=true" so it always go to database and update session cache and persistence context. You will always get fresh ones. Also it gurantees that entities are not modified in the database until transaction complete. It is a good pattern to have this as named query like below. @NamedQuery(name="Employee.lockedFindById", query="SELECT e FROM Employee e WHERE e.id = :id", hints=@QueryHint(name="toplink.pessimistic-lock", value="Lock")) As you know pessimistic locking can be performance bottleneck. So use this carefully or consider using optimistic locking. Disabling shared session cacheDissabling shared cache is the final way. It is not recommended but in some cases it requires. Think of web tier which just only reads from database and need to get latest contents per request. It even doesn't need a transaction. (Normally it uses container-managed EntityManager out of transaction, so entities are not managed). In this case I don't need session cache and want to do simple find/query without extra work like refreshing. It can be done by turning shared-cache property "toplink.cache.shared.xxx" off(xxx is entity name). If you disable shared cache, the shared session cache in server session is not used for the specified entities and instead isolated client session caches are used. An isolated cache is the session cache which a client session has. Isolated cache is TopLink's concept but in JPA it doesn't have actual meaning because the first-level cache - persistence context - is used while EntityManager(client session) is alive. For example, if you set "toplink.cache.shared.Employee=false" all Employee entities are not stored in shared cache. See the following configuration in persistence.xml. <persistence ...>
<persistence-unit name="HR">
...
<properties>
...
<property name="toplink.cache.shared.Employee" value="false"/>
<property name="toplink.cache.shared.Department" value="false"/>
</properties>
</persistence-unit>
</persistence>
CAUTION: If the entity has relationships, the associated entities should be set to false too. In this case Department should be set to false. There is also "toplink.cache.shared.default" property for all entities like below. <property name="toplink.cache.shared.default" value="false"/> But this is not recommended[*]. Also it has a bug so it doesn’t work now. Setting the cache type "toplink.cache.type.default" or "toplink.cache.type.xxx" to "NONE" has similar effect as above but this setting can result in infinite recursion if there is a cycle of eagerly loaded relationships[*]. So it not recommeded at all. Future enhancementsThe cache feature of TopLink Essentials is powerful but there are some limitations compared to commercial TopLink or Hibernate. Cache synchronization between clustered application If applications are different or applications are clustered, the session cache is not shared. TopLink provides cache synchronization and Hibernate also provides clustered 2nd-level cache. Cache invalidation policy Currently the session cache is controlled by Weak or Soft references(see cache-type options). So we cannot invalidate cache as time/daily basis or per query. TopLink provides several invalidation policies, but currently TopLink Essentials is lack of this. Query cache In TopLink Essentials every query goes to database. In some cases query results don’t change, so caching query results is good for performance. TopLink provides this feature, but currently TopLink Essentials is lack of this. I hope these features are added to TopLink Essentials in the future. ReferencesTopLink Essentials: How to use Java Logger in Java SE modePosted by guruwons on September 06, 2006 at 04:06 AM | Permalink | Comments (2)TopLink Essentials(GlassFish JPA RI) can be used also in Java SE mode as you know. I will talk about how to change the default logger to java logger in this article. When you use TopLink in Java SE, you see following log messages generated by a default logger. [TopLink Info]: 2006.09.06 05:03:05.221--ServerSession(24529889)-- Thread(Thread[main,5,main])--TopLink, version: Oracle TopLink Essentials - 2006.8 (Build 060818) [TopLink Info]: 2006.09.06 05:03:05.791--ServerSession(24529889)-- Thread(Thread[main,5,main])--file:/home/wonskim/works/toplink-essentials/samples/javalogger/classes-HR login successful ... There is no direct configuration to change this default logger in Java SE. The default logger prints logs only in standard out(System.out) and you cannot log in files. How inflexible it is! You might think. Therefore I filed an RFE for this. Workaround But there is a workaround you can use now. Let's see how GlassFish is doing to integrate TopLink logger with its Java logger. It is done through ServerPlatform extension. ServerPlatform is used for integrating TopLink with application server (SunAS9ServerPlatform is used for GlassFish). Generally it is not used in Java SE. But with ServerPlatform you can change logger type. So by supplying custom ServerPlatform class, you can use Java logger. See the following JavaLoggerServerPlatform class. package toplink;
import oracle.toplink.essentials.platform.server.ServerPlatformBase;
import oracle.toplink.essentials.internal.sessions.DatabaseSessionImpl;
import oracle.toplink.essentials.internal.databaseaccess.Platform;
import oracle.toplink.essentials.logging.JavaLog;
import oracle.toplink.essentials.logging.SessionLog;
/**
* TopLink Essentials ServerPlatform class which provides Java Logger only.
* <p/>
* This code is based from <code>NoServerPlatform</code>.
*
* @author Wonseok Kim
*/
public class JavaLoggerServerPlatform extends ServerPlatformBase {
public JavaLoggerServerPlatform(DatabaseSessionImpl newDatabaseSession) {
super(newDatabaseSession);
this.disableRuntimeServices();
;
this.disableJTA();
}
public String getServerNameAndVersion() {
return null;
}
public Class getExternalTransactionControllerClass() {
return null;
}
public void launchContainerThread(Thread thread) {
}
public SessionLog getServerLog() {
return new JavaLog();
}
public java.sql.Connection unwrapOracleConnection(Platform platform, java.sql.Connection connection){
return connection;
}
}
To compile this toplink-essentials.jar is required. Put this in your classpath and specify server platform property in persistence.xml like below. <persistence xmlns="http://java.sun.com/xml/ns/persistence"
version="1.0">
<persistence-unit name="HR">
...
<properties>
<property name="toplink.server.platform.class.name" value="toplink.JavaLoggerServerPlatform"/>
<!-- For GlassFish v2 use the following new property -->
<!--property name="toplink.target-server" value="toplink.JavaLoggerServerPlatform"/-->
<property name="toplink.logging.level" value="FINEST"/>
...
</properties>
</persistence-unit>
</persistence>
Currently there is an issue that a ServerPlatform is intialized late, so default logger is used in early stage like below before Java logger. [TopLink Finest]: 2006.09.06 05:06:05.523--ServerSession(24529889)-- Thread(Thread[main,5,main])--property=toplink.weaving; value=false [TopLink Config]: 2006.09.06 05:06:05.716--ServerSession(24529889)-- Thread(Thread[main,5,main])--The alias name for the entity class [class jpatest.entities.Employee] is being defaulted to: Employee. ... Sep 6, 2006 5:06:06 PM oracle.toplink.essentials.file:/home/wonskim/works/toplink-essentials/samples/javalogger/classes-HR INFO: TopLink, version: Oracle TopLink Essentials - 2006.8 (Build 060818) Sep 6, 2006 5:06:06 PM oracle.toplink.essentials.file:/home/wonskim/works/toplink-essentials/samples/javalogger/classes-HR FINEST: DBPlatform: oracle.toplink.essentials.platform.database.PointBasePlatform, RegularExpression: (?i)pointbase.*. Sep 6, 2006 5:06:06 PM oracle.toplink.essentials.file:/home/wonskim/works/toplink-essentials/samples/javalogger/classes-HR ... It will be fixed in GlassFish v2. Logging Level Log level can be set by "toplink.logging.level" property. But it's not enough for Java logger. You need to set handler level if you want a level below INFO. Modify JRE/lib/logging.properties file or create new one and use -Djava.util.logging.config.file=your_file. java.util.logging.ConsoleHandler.level = ALL Formatter TopLink provides also two formatters for printing out more info about its internal session.
Configure it like below if you want more! java.util.logging.ConsoleHandler.formatter = oracle.toplink.essentials.logging.TopLinkSimpleFormatterEnjoy! | |||||||||
|
|