Showing posts with label JPA. Show all posts
Showing posts with label JPA. Show all posts

Wednesday, January 07, 2009

The Nasty JPA "Unknown abstract schema type" error

I been trying to debug the following error for days, and it drive me nuts!

The problem: JPA refuse to compile one of my NamedQuery, and throws the following error:

Error compiling the query [UserVO.findByUserName: SELECT u FROM UserVO u WHERE u.name = :name]. Unknown abstract schema type [UserVO]

After numerous Google search, I conclude that JPA will throw "Unknown abstract schema type" when JPA fail locate your entity class". Most often, this type error occurs when:
  • Developer have put down database table name instead of Entity Class name in the JPA query. eg. if we have a EntityClass UserVO which map to table name users, the query "SELECT u from users u" will throw the above exception.
  • When running JPA in standalone, or not JavaEE container(such as Tomcat 5/6), they forgot to explicit list all entities class in the persistence.xml, thus JPA fail to locate the entities when compile the query.
None of above apply to my case, I have explicit listed all my entities class in the persistence.xml
and I am sure my JPA query is valid. I have tested my code with different JPA implementations, but always resulted the same error..

Here my UserVO class


@Entity(name = "users")
@NamedQuery(name = "UserVO.findByUserName",
query = "SELECT u FROM UserVO u WHERE u.name = :name")
public class UserVO extends implements Serializable {

|
|
}


If I remove the NamedQuery, my JPA able to work as expected, i.e I am able to insert, delete, and update UserVO object.
Now, to all my smart reader, can u spot what's wrong in my code? Scroll down for answer.


|
|
|
|
|
|
|

Answer: The culprit is the Entity annotation, I explicit name the UserVO entity as users, which JPA have no problem to map the UserVO to users database table, but have problem to compile the JPA Query, because it can't find the UserVO entity in the JPA context, as the suppose UserVO entity have rename to users entity by me, got it, or confuse..

To resolve this, just add a @Table annotation with the table name, as shown code below:


@Entity
@Table(name = "users")
@NamedQuery(name = "UserVO.findByUserName",
query = "SELECT u FROM UserVO u WHERE u.name = :name")
public class UserVO extends implements Serializable {

|
|
}


Cheers, Happy New Year!!!!

Tuesday, April 15, 2008

A quick update on JPA Boolean Magic Converter

Some update on changes:
  • Fixed the bug of Null pointer exception, when JPA return null on the annotated field.
  • Introduce a new properties call ifNull, which allows user to configure what to return if JPA returns null, it expect enum of org.jbpcc.util.jpa.ReturnType, which have values of ReturnType.True, ReturnType.FALSE, and ReturnType.Null,. The default value of ifNull is ReturnType.Null
Thus as an example, assuming we have model class define as below:

package org.jbpcc.domain.model;

import javax.persistence.Entity;
import javax.persistence.Id;
import org.jbpcc.util.jpa.BooleanMagic;
import org.jbpcc.util.jpa.BooleanMagic.ReturnType;

@Entity
public class SomeVO {
@Id
private Integer id;
@BooleanMagic(trueValue = "Yes", falseValue = "No",
columnName = "OVERDUED", ifNull = ReturnType.FALSE)
private transient Boolean overdued;

public Boolean isOverdued() {
return overdued;
}

public void setOverdued(Boolean overdued) {
this.overdued = overdued;
}
}

Java APT with JPABooleanMagicConverter factory, code above will be converted to:

@Entity
public class SomeVO {
@Id
private Integer id;
private transient Boolean overdued;
//--- Lines below are generated by JBPCC BooleanMagicConvertor PROCESSOR
//--- START :

@Column(name="OVERDUED")
private String magicBooleanOverdued;


public Boolean isOverdued() {
if (this.magicBooleanOverdued == null)
return false;
return this.magicBooleanOverdued.equals("Yes") ? Boolean.TRUE : Boolean.FALSE;
}

public Boolean getOverdued() {
if (this.magicBooleanOverdued == null)
return false;
return this.magicBooleanOverdued.equals("Yes") ? Boolean.TRUE : Boolean.FALSE;
}

public void setOverdued(Boolean trueFlag) {
this.magicBooleanOverdued = trueFlag ? "Yes" : "No";
}
//--- END
//--- GENERATED BY JBPCC BooleanMagicConvertor PROCESSOR

}


That's all, as usual, you could find the JPABooleanMagicConverter binary at http://code.google.com/p/jbpcc/downloads/list.

Do share me your thoughts and comments, happy coding

Sunday, April 13, 2008

A portable JPA Boolean Magic Converter

While doing some small POC with JPA, I am surprise to find out that the current Java Persistence API (JPA) standard does not mandate JPA provider to support data type conversion via annotation, not even a with simple boolean field. For reader who unfamiliar with JPa, what I means is, to persist and "JPA" manage a boolean field, JPA expect the database data type to be integer, where value of "1" means true, and value of "0" means false.

You got to be joking, right? Every "real life" programmer knows that, there are many existing database use different combination of char or string on a table boolean field, such as "True/False", "T/F", "Yes/No", "Y/N", "-1/0", and etc. Thus, as a "real life" programmer, I kind expect the JPA should allows me specific a boolean field via Annotation, such as example below:

@booleanField(trueValue="Yes", falseValue="No")
private Boolean enabled;

And to my surprise, the answer is nope!!!..So far only Hibernate JPA provider provide data type conversation. Daniel Pfeifer, have explain the problem in details, he also discuss some solutions to work around this stupid limitation at his blog, title "Type Conversation with JPA", do check it out.

Daniel's "manual mapping" idea, do solve the limitation. However, maybe it's just me, I just don't like the idea to temporary change a Boolean field to map with database data type (either char, string) in order to resolve the limitation, even we make the field private, this is just me. And I am stubborn, and old . :-)

Anywhere, after some research (Also, a good excuse to my manager to research how difficult to come out our own custom Java 5 annotation with custom Java APT process factory), I come out our own compile time annotation, called @BooleanMagic with our own BooleanMagicProcessorFactory, which I hope, shall temporary resolve the problem.

@BooleanMagic comprise of three attributes:
  • trueValue, specified the table boolean field true value here, such as "True", "T", "Yes"
  • falseValue, specified the table boolean field false value here, such as "False", "F","N"
  • columnName, tableColumn name;
Also please note that BooleanMagicProcessorFactory mandate any field annotated with @BooleanMagic annotation, must either annotated with @Transient annotation or with modifier transient.

Thus, as an example, assuming we have a boolean field called "enabled", which map to table "ENABLED" field, with boolean value of "True"/"false", we will specified our VO as below:

@Entity
public class SomeVO {
@Id
Integer id;
@BooleanMagic(trueValue="True", falseValue="false", columnName="ENABLED")
private transient Boolean enabled;

public Boolean isEnabled() {
return enabled;
}

public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
}

Using Java "APT" tool with JPABooleanMagicConverter factory, code above will be converted to:


@Entity
public class SomeVO {
@Id
Integer id;

private transient Boolean enabled;
//---
//--- Lines below are generated by JBPCC BooleanMagicConvertor PROCESSOR
//--- START :

@Column(name="ENABLED")
private String magicBooleanEnabled;

public Boolean isEnabled() {
return this.magicBooleanEnabled.equals("True") ? Boolean.TRUE : Boolean.FALSE;
}

public Boolean getEnabled() {
return this.magicBooleanEnabled.equals("True") ? Boolean.TRUE : Boolean.FALSE;
}

public void setEnabled(Boolean trueFlag) {
this.magicBooleanEnabled = trueFlag ? "True" : "false";
}

//--- END
//--- GENERATED BY JBPCC BooleanMagicConvertor PROCESSOR
}


Now, isn't that cool (haha, sorry, I am bit biased here). Btw, BooleanMagicConvertor is portable across JPA protable, and IMHO, this is a better interim solution till JPA make data conversation standard.

I decided to contribute my BooleanMagicConverter back to open source community, it's currently park under my open source project Java Batch Process Control Center, at http://code.google.com/p/jbpcc, you could either build from the source, or get the "BooleanMagicProcessorFactory.jar" from here.

Here an example of how to use the BooleanMagicConvertor using Ant 1.7 apt tasks, assuming all your entity class is declare at ${src.dir}/org/abc/domain/model directory, and you wants to output all generated call to ${basedir}/target directory
 <target name="preProcessJPABoolean" depends="buildJPABoolenProcessorFactory">
<apt srcdir="${src.dir}/org/abc/domain/model"
destdir="${build.classes.dir}"
classpath="./dist/BooleanMagicProcessorFactory.jar:./lib/Model_Dependent_thirdparty.jar"
debug="on"
compile="false"
factory="org.jbpcc.util.jpa.BooleanMagicProcessorFactory"
preprocessdir="${basedir}/target">
</apt>
</target>

That's all, do share me your thoughts and comments, cheers!