The following JPA criteria query succeeds on Hibernate (4.2.7 final).
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<Boolean>criteriaQuery=criteriaBuilder.createQuery(Boolean.class);
Root<UserTable> root = criteriaQuery.from(entityManager.getMetamodel().entity(UserTable.class));
criteriaQuery.multiselect(root.get(UserTable_.enabled));
ParameterExpression<String>parameterExpression=criteriaBuilder.parameter(String.class);
criteriaQuery.where(criteriaBuilder.equal(criteriaBuilder.lower(criteriaBuilder.trim(root.get(UserTable_.emailId))), criteriaBuilder.lower(criteriaBuilder.trim(parameterExpression))));
List<Boolean> list = entityManager.createQuery(criteriaQuery).setParameter(parameterExpression, "admin").getResultList();
for(Boolean o:list)
{
System.out.println("enabled : "+o);
}
It is simply meant to return a scalar Boolean value from MySQL database. The corresponding column is of type TINYINT(1) in MySQL.
It generate the following SQL statement.
SELECT
usertable0_.enabled as col_0_0_
FROM
social_networking.user_table usertable0_
WHERE
lower(trim(BOTH FROM usertable0_.email_id))=lower(trim(BOTH FROM ?))
The same query fails on EclipseLink (2.5.1) in which case it returns nothing (no error, no exception). It however, generates a correct SQL statement as follows.
SELECT enabled
FROM projectdb.user_table
WHERE (LOWER(TRIM(email_id)) = LOWER(TRIM(?)))
bind => [admin]
The corresponding JPQL like so,
SELECT u.enabled
FROM UserTable u
WHERE lower(trim(u.emailId))=lower(trim(:emailId))
also doesn't get the value in question.
It however, works with additional columns as shown below.
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]>criteriaQuery=criteriaBuilder.createQuery(Object[].class);
Root<UserTable> root = criteriaQuery.from(entityManager.getMetamodel().entity(UserTable.class));
criteriaQuery.multiselect(root.get(UserTable_.enabled), root.get(UserTable_.firstName));
ParameterExpression<String>parameterExpression=criteriaBuilder.parameter(String.class);
criteriaQuery.where(criteriaBuilder.equal(criteriaBuilder.lower(criteriaBuilder.trim(root.get(UserTable_.emailId))), criteriaBuilder.lower(criteriaBuilder.trim(parameterExpression))));
List<Object[]> list = entityManager.createQuery(criteriaQuery).setParameter(parameterExpression, userName).getResultList();
One extra column root.get(UserTable_.firstName) is added and the return type of CriteriaQuery is changed from CriteriaQuery<Boolean> to CriteriaQuery<Object[]>.
The column is defined as follows in the corresponding entity.
#Basic(optional = false)
#NotNull
#Column(name = "enabled")
private Boolean enabled; //Getter and setter.
Why doesn't it given a Boolean value in EclipseLink?
This is due to EclipseLink bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=340089 which causes EclipseLink to be unable to distinguish the value returned from the SQL query from the construct it uses internally to indicate there were no results. Selecting another value is the only workaround, but it seems simple enough that not many people hit it or just workaround it without commenting or voting for the bug.
Related
I am trying to do an easy search on a table that can be on any kind of database. The following query is working an the most databases, but I cannot find a solution which works on mysql.
The tables in my database are generated by the active objects framework, so I cannot change the names or config of those instances.
Here is the query that works fine on all databases but MySQL:
select * from "AO_69D057_FILTER" where "SHARED" = true AND "CONTAINS_PROJECT" = true AND UPPER("FILTER_NAME") like UPPER('%pr%').
MySql is not able to use the table name in double quotes for some reason. If I use the unquoted table name it works on MySQL but not on Postgres. Postgres is converting the table name to lowercase because it is unquoted. AO is generating the table names in upper case.
I also tried to use an alias, but that can not work because of the evaluation hierarchy of the statement.
Any suggestions how to get rid of the table name problem?
By default double quotes are used to columns.
You can change it:
SET SQL_MODE=ANSI_QUOTES;
Here is the documentation about it:
http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html
I had the same problem. I select the query according to the exception I get. In the first call of the db search, I try without quotes if it fails then I try with quotes. Then I set useQueryWithQuotes variable accordingly so that in future calls I do not need to check the exception. Below is the code snipped I am using.
private Boolean useQueryWithQuotes=null;
private final String queryWithQuotes = "\"OWNER\"=? or \"PRIVATE\"=?";
private final String queryWithoutQuotes = "OWNER=? or PRIVATE=?";
public Response getReports() {
List<ReportEntity> reports = null;
if(useQueryWithQuotes==null){
synchronized(this){
try {
reports = new ArrayList<ReportEntity>( Arrays.asList(ao.find(ReportEntity.class, Query.select().where(queryWithoutQuotes, getUserKey(), false))) );
useQueryWithQuotes = false;
} catch (net.java.ao.ActiveObjectsException e) {
log("exception:" + e);
log("trying query with quotes");
reports = new ArrayList<ReportEntity>( Arrays.asList(ao.find(ReportEntity.class, queryWithQuotes, getUserKey(), false)));
useQueryWithQuotes = true;
}
}
}else{
String query = useQueryWithQuotes ? queryWithQuotes : queryWithoutQuotes;
reports = new ArrayList<ReportEntity>( Arrays.asList(ao.find(ReportEntity.class, query, getUserKey(), false)));
}
...
}
I want to get multiple columns from database in a single query and set it to the corresponding DTO object fields.
Error message:
java.lang.IllegalStateException: No data type for node:
org.hibernate.hql.internal.ast.tree.IdentNode
+-[IDENT] IdentNode: 'payment' {originalText=payment}
Query:
TypedQuery<Object[]> query = entityManager.createQuery("SELECT
payment, createdOn,responseMessage FROM PaymentLog log WHERE log.id
=:personId", Object[].class);
query.setParameter("personId",new BigInteger(basicEntityDto.getId()));
List<Object[]> results = query.getResultList();
for (Object[] log : results) {
paymentTransaction.setAmount(log[0].toString());
paymentTransaction.setDate(log[1].toString());
paymentTransaction.setDescription(log[2].toString());
transactionList.add(paymentTransaction);
}
P.S. I know I can use JPA constructor expression. But as I have to add the DTOs in a list of DTO(i.e. transactionList), so is there a way with JPA construction expression where I can do that by running the query only one time instead in a loop for every single DTO?
You can have the JPA provider transform the result set for you by means of a constructor expression:
http://www.objectdb.com/java/jpa/query/jpql/select#Result_Classes_Constructor_Expressions_
https://en.wikibooks.org/wiki/Java_Persistence/JPQL#Constructors
This requires that the specified class has a constructor matching the select expression. This would then look something like the below:
TypedQuery<PaymentTransaction> query = entityManager.createQuery("SELECT new PaymentTransaction (log.payment, log.createdOn, log.responseMessage ) FROM PaymentLog log WHERE log.id
=:personId", PaymentTransaction.class);
query.setParameter("personId",new BigInteger(basicEntityDto.getId()));
List<PaymentTransaction> results = query.getResultList();
In JPA 2.1 you can also so like the below:
https://en.wikibooks.org/wiki/Java_Persistence/Querying#ConstructorResult_.28JPA_2.1.29
What you could do is:
TypedQuery<PaymentLog> query = entityManager.createQuery("SELECT log FROM PaymentLog log WHERE log.id =:personId", PaymentLog.class);
query.setParameter("personId",new BigInteger(basicEntityDto.getId()));
List<PaymentLog> results = query.getResultList();
for (PaymentLog log : results) {
paymentTransaction.setAmount(log.getPayment());
paymentTransaction.setDate(log.getCreatedOn());
paymentTransaction.setDescription(log.getResponseMessage());
transactionList.add(paymentTransaction);
}
It is not a good idea to select everything from the database if you are not going to use it. If the selected fields were the only columns in the table then approach above works.
If you had a lot more columns in the table, the previous would still work, but this might be better:
TypedQuery<PaymentTransaction> query = entityManager.createQuery("SELECT new PaymentTransaction (log.payment, log.createdOn, log.responseMessage) FROM PaymentLog log WHERE log.id =:personId", PaymentTransaction.class);
query.setParameter("personId",new BigInteger(basicEntityDto.getId()));
List<PaymentTransaction> results = query.getResultList();
The above query will return an already created list of PaymentTransactions. You have to note that the class PaymentTransactionshould have a constructor that accept these fields in the given order. Otherwise it will cause an exception
I am trying to update data to a mySQL database using JPA. I have no problem persisting data but flush will not work as expected. I retrieve the id for the login session, set that id (it is the primary key) along with setting the description field that I want merged to the database. I have debugged line by line through this method and all variables contain the expected values. Any ideas or suggestions to overcome this problem are appreciated.
public String update() {
factory = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
EntityManager em = factory.createEntityManager();
if(true){
em.getTransaction().begin();
String sessionEmail=Util.getEmail();
//Create query to find user passwords matching the inputted name
Query myQuery = em.createQuery("SELECT u FROM BusinessAccount u WHERE u.email=:email");
myQuery.setParameter("email", sessionEmail);
List<BusinessAccount> accounts=myQuery.getResultList();
int intId=accounts.get(0).getId();
businessAccount.setId(intId);
String des=businessAccount.getDescription();
businessAccount.setDescription(des);
em.flush();
addMessage(new FacesMessage(FacesMessage.SEVERITY_INFO,
"User Registration Successful!", null));
return "success";
}
else {
addMessage(new FacesMessage(FacesMessage.SEVERITY_ERROR,
"User Registration Failed!", null));
return "failure";
}
}
merge() persists all the state of the entity. Not just the non-null fields. I it wasn't, you would complain that you want to set some field to null and that merge() ignores it and leaves it as is.
So get an entity from the database, and modify it, instead of only gettings its ID, creating a new entity instance from scratch and only settings some of its fields.
Note that, if you get the entity and modify it inside a single transaction, you don't even have to call merge(): the new state will be made persistent automatically.
I am using JPA (Hibernate) to store an entity on a MySQL 5.0 server.
Here is the entity in simplified form:
#Entity
#Table(name = "messages")
public class Message implements Serializable
{
#Id
#GeneratedValue
#Column
private long id;
#Column
private String content;
#Column(insertable = false)
private Date read;
#Column(insertable = false)
private Date deleted;
}
The columns "read" and "deleted" in table "messages" are defined so that they can contain NULL values. When I first tried to persist one of these entities I got an exception. Apparently, what Hibernate was doing there was listing the "read" and "deleted" columns in the column list of the insert statement but not in the value list. I got around that problem with the "insertable = false" statement in the #Column annotations you see above.
Now, however, I have a bigger problem. I want to set the read or date fields to non-null values. When I do that, I get a similar exception "You have an error in your SQL syntax". What he is doing now is listing all fields in the where part of the update statement, including "read" and "deleted". And what he does is check like "...and read=NULL". Which, in MySQL, of course should be "...and read IS NULL".
Rummaging around, I already found the "updatable" parameter for the #Column annotation. But if I set that to false, both "read" and "deleted" are never updated at all, so that is not what I am looking for, either.
...Help?
read is a reserved word in mysql and it looks like the driver is not escaping the name with back quotes. I think the best solution is to rename the column.
I have the following JPA SqlResultSetMapping:
#SqlResultSetMappings({
#SqlResultSetMapping(name="GroupParticipantDTO",
columns={
#ColumnResult(name="gpId"),
#ColumnResult(name="gpRole"),
// #ColumnResult(name="gpRemarks")
}
)
Which is used like this:
StringBuilder sbQuery = new StringBuilder("Select ");
sbQuery.append(" gpId, ");
sbQuery.append(" gpRole, ");
// sbQuery.append(" gpRemarks ");
sbQuery.append(" FROM v_group_participants_with_details ");
Query query = em.createNativeQuery(sbQuery.toString(), "GroupParticipantDTO");
The view is like this:
DROP VIEW IF EXISTS `v_group_participants_with_details`;
CREATE VIEW `v_group_participants_with_details`
AS
SELECT
gp.id AS gpId,
gp.role AS gpRole,
gp.remarks AS gpRemarks
FROM GroupParticipation gp
;
The GroupParticipation table has the remarks column defined as LONGTEXT (I'm using Mysql 5.x)
Now for the problem:
When the remarks field is commented out from the query everything works perfectly, but if I try to include the remarks field in the query, I get the following error:
javax.persistence.PersistenceException: org.hibernate.MappingException:
No Dialect mapping for JDBC type: -1
at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException
(AbstractEntityManagerImpl.java:614)
at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:76)
What gives? How can I get a LONGTEXT column from a native query?
This problem is reported in HHH-1483 and HHH-3892. In short, Hibernate does not know, how to map a LONGVARCHAR column returned by a native query.
This problem is fixed in Hibernate 3.5.0+. For previous versions, a workaround would be to extend the MysqlDialect to register the correct Hibernate Type for a LONGVARCHAR:
import java.sql.Types;
import org.hibernate.Hibernate;
public class MyMySQL5Dialect extends org.hibernate.dialect.MySQL5Dialect {
public MyMySQL5Dialect() {
super();
// register additional hibernate types for default use in scalar sqlquery type auto detection
registerHibernateType(Types.LONGVARCHAR, Hibernate.TEXT.getName());
}
}