i'm using MondoDB with Morphia 0.105 layer.
My User class is:
#Entity
public class User {
#Id
private ObjectId id = null;
private String userId = null;
private String fullName = null;
#Embedded
private UserType userType = null;
#Embedded
private Set<Rights> rights = new HashSet<Rights>();
and my test class is:
public class Test {
public static void main(String[] args) throws UnknownHostException {
Morphia m = new Morphia();
Datastore ds = m.createDatastore(new MongoClient("localhost"), "test");
m.map(User.class);
User u = new User();
u.setFullName("User Name");
u.setUserId("USERID");
//u.getRights().add(Rights.ADMIN); //NO rights added VS ONE right added
ds.save(u);
u = ds.find(User.class).filter("rights size", 0).get();
System.out.println(u);
System.out.println(u != null);
}
}
I got this unexpected result:
The type(s) for the query/update may be inconsistent; using an instance of type 'java.lang.Integer' for the field 'org.vts.sis2.entities.User.rights' which is declared as 'java.util.Set'
If i uncomment line //u.getRights().add(Rights.ADMIN); that adds to user a right, the query ds.find(User.class).filter("rights size", 1).get() returns the correct result (even if the warning is displayed, but it's a false positive since in my opinion it's correct to compare a result of size operator to an integer)!
What should i do to query users with empty rights list/set field?
Thanks
Try this:
ds.find(User.class).field("rights").sizeEq(0).get()
Related
I have a class that I am attempting to query by "userid"
#Entity
#IdClass(CollectionPK.class)
#Table(name="collection", schema="mageduelsusers")
public class Collection{
#Id
#Column(name = "userid")
private int userId;
#Id
#Column(name = "cardid")
private int cardId;
...
Id class of
public class CollectionPK implements Serializable{
private int userId;
private int cardId;
public CollectionPK() {
}
...
Query code is
public List<Collection> readCollection(int id) {
List<Collection> collection = null;
Session session = factory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Collection> criteriaQuery = builder.createQuery(Collection.class);
Root<Collection> root = criteriaQuery.from(Collection.class);
ParameterExpression userIdParameter = builder.parameter(Collection.class);
criteriaQuery.where(builder.equal(root.get("userid"), userIdParameter));
Query<Collection> query = session.createQuery(criteriaQuery);
query.setParameter("userid", id);
collection = query.getResultList();
tx.commit();
}
...
Error is
Exception in thread "main" java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [userid] on this ManagedType [com.panda.userinfo.Collection]
Ideal query would be
Select * from collection where userid = 'userid';
How do I modify to make this work?
Pretty sure error is in the criteria builder section as session.save(). session.get(), and session.delete() all work properly
Update:
Did a little bit of testing and the cause of the issue is definitely root.get("userid") Is there any way to check what Attributes hibernate has for a class?
Update2:
Capitalizing the I in root.get("userId") fixes that error. However both forms still cause an error at query.setParameter("userId", id)
java.lang.IllegalArgumentException: Unable to locate parameter registered with that name [userId]
Update 3:
Figured it out or at least made it functional. Hibernate was renaming things in the background. Solved by printing everything to find the correct parameter name.
for(Parameter<?> p:query.getParameters()) {
System.out.println(p.getName());
}
System.out.println(query.getParameters().size());
Try to correct your query in this way:
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Collection> criteriaQuery = builder.createQuery(Collection.class);
Root<Collection> root = criteriaQuery.from(Collection.class);
ParameterExpression<Integer> userIdParameter = builder.parameter(Integer.class);
criteriaQuery.where(builder.equal(root.get("userid"), userIdParameter));
List<Collection> collection = session.createQuery(criteriaQuery)
.setParameter("userid", id)
.getResultList();
See also this section of the documentation.
I have created the object Person, I can deleted and modify it and I can also search for Person by his name or phonenumber... but I don't know for exemple how to search for a person by his ** home address**. Here is my code:
My entity Person.java:
public class Person{
private Long id;
private String name;
#ManyToOne(cascade = CascadeType.ALL)
private Address address;
....
}
My entity Address.java
public class Address{
...
private String streetName;
...
}
And here is the most interesting function that I am trying to modify to get what I want, I would like to search for Persons who live in xxx (streetName = xxx). Here is my function getByQuery:
public List<Person> getByQuery(PersonSearchQuery searchQuery) {
Map<String, String> criteriaQuery = new HashMap<String, String>();
if (searchQuery.getName() != null)
criteriaQuery.put("name",searchQuery.getName());
TypedQuery<Person> query = this.findByQuery(criteriaQuery);
return query.getResultList();
}
The object PersonSearchQuery contains just to attributes name (String) and streetName (String) and their getters.
Function findByQuery:
public TypedQuery<T> findByQuery(Map<String, String> criteriaQuery) {
CriteriaBuilder builder = this.em.getCriteriaBuilder();
CriteriaQuery<T> criteria = builder.createQuery(this.entityClass);
Root<T> root = criteria.from(this.entityClass);
criteria.select(root);
Predicate predicate = builder.conjunction();
if (criteriaQuery.size() != 0) {
for (String key : criteriaQuery.keySet()) {
try{
predicate = builder.and(predicate, builder.equal(root.<String>get(key), criteriaQuery.get(key)));
}catch(IllegalArgumentException e){
continue;
}
}
}
criteria.where(predicate);
return this.em.createQuery(criteria);
}
So I can search for Persons by their names by I cannot search for them by streetName the problem is my function getByQuery I would like to do something like this:
if (searchQuery.getStreetName() != null)
criteriaQuery.put("Address.streetName",searchQuery.getStreetName());
The problem is I don't know how to define the key in this case. Thanks for your help
I only use CriteriaBuilder if I have several similar Entities which needs to be used/rendered in the same way, so if person is the only Entity with an Address reference I would just use JPQL, like this:
entityManager.createQuery(
"select p from Person p where p.address.streetName like :streetName", Person.class)
.setParameter("streetName", "xyz" + "%").getResultList()
The main reason I tend to avoid CriteriaBuilder, is because it has a rather steep learning curve, and you need to write a lot of code to express very simple concepts. In contrast any developer familiar with SQL can read and maintain JPQL code.
These days I always use frameworks, like DeltaSpike Data (for EE) and Spring Data, they both implements most of the basic DAO/Repository features, so If you don't mind an extra dependency (and some magic) it can save you a lot of boilerplate JPA code.
Today I stumbled over some unexpected behaviour of EclipseLink. (I don't know if this is bound to EclipseLink or if this is the same for all JPA providers.)
I assumed that retrievals of a managed JPA bean always return references to the same object instance when issued inside the same transaction (using the same EntityManager).
If that is right, I don't know why I receive an error when I execute the following test case:
#Test
public void test_1() {
EntityManager em = newEntityManager();
em.getTransaction().begin();
// Given:
Product prod = newProduct();
// When:
em.persist(prod);
em.flush();
Product actual =
em.createQuery("SELECT x from Product x where x.id = "
+ prod.getId(), Product.class).getSingleResult();
// Then:
assertThat(actual).isSameAs(prod); // <-- FAILS
em.getTransaction().commit();
}
The statement marked with "FAILS" throws the following AssertionError:
java.lang.AssertionError:
Expecting:
<demo.Product#35dece42>
and actual:
<demo.Product#385dfb63>
to refer to the same object
Interestingly the following slightly modified test succeeds:
#Test
public void test_2() {
EntityManager em = newEntityManager();
em.getTransaction().begin();
// Given:
Product prod = newProduct();
// When:
em.persist(prod);
em.flush();
Product actual = em.find(Product.class, prod.getId());
// Then:
assertThat(actual).isSameAs(prod); // <-- SUCCEEDS
em.getTransaction().commit();
}
Obviously there is a difference between finding and querying objects.
Is that the expected behaviour? And why?
--Edit--
I think I found the source of the problem: Product has an ID of type ProductId.
Here is the relevant code:
#Entity
#Table(name = "PRODUCT")
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID", nullable = false)
#Converter(name = "productIdConverter", converterClass = ProductIdConverter.class)
#Convert("productIdConverter")
private ProductId id;
#Column(name = "NAME", nullable = false)
private String name;
[...]
}
The #Convert and #Converter annotations are EclipseLink-specific.
Unlike JPA 2.1 Converters you may place them on ID fields.
But it seems that in certain circumstances EclipseLink has problems to find a managed bean in its session cache if that bean uses a custom type for its ID field.
I guess I have to file a bug for that.
I found the cause of the problem and a solution.
We are using a custom ID class (ProductId) for Product, together with a custom (EclipseLink-specific) Converter-Class ProductIdConverter which has a bad implementation of the convertObjectValueToDataValue(...) method.
Here is the relevant code:
/**
* Convert the object's representation of the value to the databases' data representation.
*/
#Override
public final Object convertObjectValueToDataValue(Object objectValue, Session session) {
if (objectValue == null) {
return null;
}
Long longValue = ((ProductId) objectValue).getLong();
return longValue;
}
Please note that the method returns Long instances (or null).
But since we are using Oracle as our database backend and have declared the product's ID column as NUMBER, the JDBC Driver maps the column value as BigDecimal. This means, we have to make sure, that our convertObjectValueToDataValue(...) also returns BigDecimal instances.
So the correct implementation is:
/**
* Convert the object's representation of the value to the databases' data representation.
*/
#Override
public final Object convertObjectValueToDataValue(Object objectValue, Session session) {
if (objectValue == null) {
return null;
}
Long longValue = ((ProductId) objectValue).getLong();
return BigDecimal.valueOf(longValue);
}
Now this method returns only BigDecimal instances.
I'm using EclipseLink to run some Native SQL. I need to return the data into a POJO. I followed the instructions at EclipseLink Docs, but I receive the error Missing descriptor for [Class]
The query columns have been named to match the member variables of the POJO. Do I need to do some additional mapping?
POJO:
public class AnnouncementRecipientsFlattenedDTO {
private BigDecimal announcementId;
private String recipientAddress;
private String type;
public AnnouncementRecipientsFlattenedDTO() {
super();
}
public AnnouncementRecipientsFlattenedDTO(BigDecimal announcementId, String recipientAddress, String type) {
super();
this.announcementId = announcementId;
this.recipientAddress = recipientAddress;
this.type = type;
}
... Getters/Setters
Entity Manager call:
public List<AnnouncementRecipientsFlattenedDTO> getNormalizedRecipientsForAnnouncement(int announcementId) {
Query query = em.createNamedQuery(AnnouncementDeliveryLog.FIND_NORMALIZED_RECIPIENTS_FOR_ANNOUNCEMENT, AnnouncementRecipientsFlattenedDTO.class);
query.setParameter(1, announcementId);
return query.getResultList();
}
I found out you can put the results of a Native Query execution into a List of Arrays that hold Objects. Then one can iterate over the list and Array elements and build the desired Entity objects.
List<Object[]> rawResultList;
Query query =
em.createNamedQuery(AnnouncementDeliveryLog.FIND_NORMALIZED_RECIPIENTS_FOR_ANNOUNCEMENT);
rawResultList = query.getResultList();
for (Object[] resultElement : rawResultList) {
AnnouncementDeliveryLog adl = new AnnouncementDeliveryLog(getAnnouncementById(announcementId), (String)resultElement[1], (String)resultElement[2], "TO_SEND");
persistAnnouncementDeliveryLog(adl);
}
You can only use native SQL queries with a class if the class is mapped. You need to define the AnnouncementRecipientsFlattenedDTO class as an #Entity.
Otherwise just create the native query with only the SQL and get an array of the data back and construct your DTO yourself using the data.
Old question but may be following solution will help someone else.
Suppose you want to return a list of columns, data type and data length for a given table in Oracle. I have written below a native sample query for this:
private static final String TABLE_COLUMNS = "select utc.COLUMN_NAME, utc.DATA_TYPE, utc.DATA_LENGTH "
+ "from user_tab_columns utc "
+ "where utc.table_name = ? "
+ "order by utc.column_name asc";
Now the requirement is to construct a list of POJO from the result of above query.
Define TableColumn entity class as below:
#Entity
public class TableColumn implements Serializable {
#Id
#Column(name = "COLUMN_NAME")
private String columnName;
#Column(name = "DATA_TYPE")
private String dataType;
#Column(name = "DATA_LENGTH")
private int dataLength;
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
public String getDataType() {
return dataType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public int getDataLength() {
return dataLength;
}
public void setDataLength(int dataLength) {
this.dataLength = dataLength;
}
public TableColumn(String columnName, String dataType, int dataLength) {
this.columnName = columnName;
this.dataType = dataType;
this.dataLength = dataLength;
}
public TableColumn(String columnName) {
this.columnName = columnName;
}
public TableColumn() {
}
#Override
public int hashCode() {
int hash = 0;
hash += (columnName != null ? columnName.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
if (!(object instanceof TableColumn)) {
return false;
}
TableColumn other = (TableColumn) object;
if ((this.columnName == null && other.columnName != null) || (this.columnName != null && !this.columnName.equals(other.columnName))) {
return false;
}
return true;
}
#Override
public String toString() {
return getColumnName();
}
}
Now we are ready to construct a list of POJO. Use the sample code below to construct get your result as List of POJOs.
public List<TableColumn> findTableColumns(String table) {
List<TableColumn> listTables = new ArrayList<>();
EntityManager em = emf.createEntityManager();
Query q = em.createNativeQuery(TABLE_COLUMNS, TableColumn.class).setParameter(1, table);
listTables = q.getResultList();
em.close();
return listTables;
}
Also, don't forget to add in your POJO class in persistence.xml! It can be easy to overlook if you are used to your IDE managing that file for you.
Had the same kind of problem where I wanted to return a List of POJOs, and really just POJOs (call it DTO if you want) and not #Entity annotated Objects.
class PojoExample {
String name;
#Enumerated(EnumType.STRING)
SomeEnum type;
public PojoExample(String name, SomeEnum type) {
this.name = name;
this.type = type;
}
}
With the following Query:
String query = "SELECT b.name, a.newtype as type FROM tablea a, tableb b where a.tableb_id = b_id";
Query query = getEntityManager().createNativeQuery(query, "PojoExample");
#SuppressWarnings("unchecked")
List<PojoExample> data = query.getResultList();
Creates the PojoExample from the database without the need for an Entity annotation on PojoExample. You can find the method call in the Oracle Docs here.
edit:
As it turns out you have to use #SqlResultSetMapping for this to work, otherwise your query.getResultList() returns a List of Object.
#SqlResultSetMapping(name = "PojoExample",
classes = #ConstructorResult(columns = {
#ColumnResult(name = "name", type = String.class),
#ColumnResult(name = "type", type = String.class)
},
targetClass = PojoExample.class)
)
Just put this anywhere under your #Entity annotation (so in this example either in tablea or tableb because PojoExample has no #Entity annotation)
I am using tje JPA criteria API to create an "IN" query. I want to select Courses that are in certain Categories. The Categories are supposed to end up in the IN part of the query.
This is the Course entity. It has a reference to a Category entity, because each Course is in one Category.
#Entity
public class Course implements DomainObject {
private Long id;
private Integer version;
private String name;
private Category category;
#Override
#Id
#GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#ManyToOne
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
}
In my service I want to select Courses that are belong to certain (a list) of Categories.
public List<Course> findCourses(CourseFilter filter) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Course> criteriaQuery = criteriaBuilder.createQuery(Course.class);
Root<Course> root = criteriaQuery.from(Course.class);
List<Predicate> predicateList = new ArrayList<Predicate>();
if (!filter.getCategories().isEmpty()) {
Predicate predicate = root.get(Course_.category).in(filter.getCategories());
predicateList.add(predicate);
}
Predicate[] predicates = new Predicate[predicateList.size()];
predicateList.toArray(predicates);
criteriaQuery.where(predicates);
TypedQuery<Course> typedQuery = entityManager.createQuery(criteriaQuery);
return typedQuery.getResultList();
}
When the query executes on the last line of the method it throws an error:
HTTP Status 500 - Request processing failed; nested exception is
org.springframework.dao.InvalidDataAccessApiUsageException:
org.hibernate.TransientObjectException: object references an unsaved transient instance
save the transient instance before flushing:nl.codebasesoftware.produx.domain.Category;
nested exception is java.lang.IllegalStateException:
org.hibernate.TransientObjectException: object references an unsaved transient instance
save the transient instance before flushing: nl.codebasesoftware.produx.domain.Category
I am not even sure I am using the right way to create an IN query. I think the criteria API is terribly complicated. But before I worry about the IN query I would like to know why Hibernate is throwing this TransientObjectException. The filter.getCategories() call results in actual categories, filled with a primary key id, etc.
Added:
Here is how I get the Category instance that I use to later fetch Courses with. This is also a DAO method that is called via a #Service from a #Controller method.
public Category findByName(String name) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Category> query = builder.createQuery(Category.class);
Root<Category> root = query.from(Category.class);
Predicate predicate = builder.equal(root.get(Category_.urlTitle), name);
query.where(predicate);
TypedQuery<Category> typedQuery = entityManager.createQuery(query);
return getSingleResult(typedQuery);
}
So, Hibernate is telling me I am using Category objects that somehow reference an unsaved entity, but I don't see how. The Category that is returned from this method is just a Category that if fetched by Hibernate. I am not doing anything with it before I send it to the method that fetches Courses.
Here is my the controller method:
#RequestMapping(method = RequestMethod.GET, value = "/{categoryUrlName}")
public String setup(#PathVariable("categoryUrlName") String categoryUrlName, Model model){
// Fetch the category
Category category = categoryService.findByName(categoryUrlName);
// if no category found, throw a 404
if(category == null){
throw new ResourceNotFoundException();
}
// Fetch courses in this category
List<Course> courses = courseService.findCourses(category);
model.addAttribute("courses", courses);
model.addAttribute("category", category);
model.addAttribute("mainContent", "content/category");
return "main";
}
Before executing a query, Hibernate flushes the changes you made to persistent entities in the session. This ensures that the query will search on the latest state of all the entities. Unfortunately, one of the dirty entities that Hibernate tries to flush references a transient entity, and thus can't be flushed, which causes the exception. The exception doesn't come from the query itself, but from the flush before the execution of the query.
You probably did something like the following before executing the query:
Cat cat = em.find(Cat.class, catId); // cat is a persistent persistent entity
cat.setMate(new Mouse()); // the mouse has not been persisted, and cat references it.