JPA select query - java

I am creating this subject after 2h of research in vain.
I am trying to do a very simple car location code which could save, remove get data from database.
So my problem is I have a parent class named Vehicule and 2 children class : car and van.
Below, there are the beginning of my 3 classes
// vehicule
#Entity
#Table(name="Vehicule")
#DiscriminatorColumn(name="vehicule_type")
#Inheritance
public class Vehicule implements Serializable{
// some constructors setters and getters here
}
// car
#Entity
#DiscriminatorValue("Car")
public class Car extends Vehicule{
#Column(name = "number_of_seats")
private int nbOfSeats;
// some constructors setters and getters here
}
// van
#Entity
#DiscriminatorValue("Van")
public class Van extends Vehicule{
#Column(name = "max_weight")
private int maxWeight;
// some constructors setters and getters here
}
I stored them in a single table with inheritanceStratregy = single table.
Now, i would like to select only cars or only vans.
I tried
Query query = em.createQuery("SELECT v FROM Vehicule v WHERE v.vehicule_type = 'Car'");
List<Car> list = (List<Car>) query.getResultList();
but i got
Caused by: java.lang.IllegalArgumentException: An exception occurred while creating a query in EntityManager:
The state field path 'v.vehicule_type' cannot be resolved to a valid type.
I also tried
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Car> q = cb.createQuery(Car.class);
Root<Car> c = q.from(Car.class);
ParameterExpression<String> p = cb.parameter(String.class);
q.select(c).where(cb.equal(c.get("vehicule_type"), p));
TypedQuery<Car> query = em.createQuery(q);
query.setParameter(p, "Car");
List<Car> results = query.getResultList();
but i got
Caused by: java.lang.IllegalArgumentException: The attribute [vehicule_type] is not present in the managed type
What am I doing wrong ?
Thanks in advance !

I cannot see the complete fields of your "Vehicule" entity.
But if you have on your Vehicule table, a field name "vehicule_type"
You ust have to specify it in your code with name "vehiculeType"
Just try to change "vehicule_type" to "vehiculeType"
But for more help, I think that you must provide the content of your Vehicle entity class.
Cheers

Is there any field with the name vehicule_type? I doubt that it is not present and somehow you are trying to access it which is causing the exception.

You never need to insert where criterias for discriminator values. JPA do this itself. You just have to select the right entity.
In your case you should just select: "SELECT v FROM Car v".
If you turn on show_sql in your configuration, you will see that JPA adds where aliasXX.vehicule_type = 'Car' automatically

Related

JPA Criteria Query with JOIN on Polymorphic Subclass

I have the following (simplified) data model:
#Entity
public class Person
{
#ManyToOne Animal likes;
}
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
public class Animal{}
#Entity
public class Pet extends Animal
{
#ManyToOne Home home;
}
#Entity
public class Domestic extends Animal
{
#ManyToOne Farm farm;
}
#Entity public class Home {}
#Entity public class Farm {}
Now I want a group the persons by home and count on different homes:
CriteriaBuilder cB = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cQ = cB.createTupleQuery();
Root<Person> person = cQ.from(Person.class);
Join<Person,Pet> jPet = person.join("likes");
Join<Pet,Home> jHome = jPet.join("home");
Here I'm getting the error
Unable to locate Attribute with the the given name [home] on this ManagedType [Animal] the generated SQL query obviously explains why:
INNER JOIN animal a ON person.likes_id = animal.id. The next approach was to use .treat:
Join<Person,Pet> jPet = cB.treat(debit.join("likes), Pet.class);
Join<Pet,Home> jHome = jPet.join("home");
This seemed to be promising with the new .treat feature of JPA 2.1, but the generated SQL looks like this
INNER JOIN animal ON person.likes_id = animal.id
LEFT OUTER JOIN pet ON pet.id = animal.id
LEFT OUTER JOIN domestic ON domestic.id = animal
In fact it is joining all subclasses of Animal where I'm only interested in Pet. So I tried to limit to the specific class with a Predicate cB.equal(jPet.type(),Pet.class). This generates some interesting SQL (which I have never seen before and where I wonder what is inserted in END=?)
WHERE CASE
WHEN pet.id IS NOT NULL THEN 1
WHEN domestic.id IS NOT NULL THEN 2
WHEN animal IS NOT NULL THEN 0
END = ?
But it still does not work as expected. How can I count the different home with JPA criteria queries where I start from a Person?
You may use multiple roots (mine: I believe that is the most common scenario) in Criteria API query:
Create and add a query root corresponding to the given entity, forming a cartesian product with any existing roots.
So, your query might look like:
Root<Person> person = cQ.from(Person.class);
Root<Pet> pet = cQ.from(Pet.class);
cQ.where(cB.equal(pet, person.get("likes")));
Moreover, that is not required to define X-TO-Y associations in order to write a query - we may join "unrelated" entities as well.

Select only some columns from a table

Is there a way to select only some columns from a table using jpa?
My tables are huge and I am not allowed to map all the columns in my entities. I tried to create an entity (as a side note, I don't have PKs in my tables):
#Entity
#Table(name = "SuperCat")
#Getter
#Setter
public class Cat{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Column(name="nameCat")
private String name;
}
and then in my repository to
public interface CatRepository extends
CrudRepository<Cat, Long> {
#Query(
"SELECT name FROM Cat")
Page<Cat> getAlCats(Pageable pageable);
This is only a simple example, but the idea is the same. I have searched a lot and I found projections, but there you need to map the whole table, then I found native queries, but still doesn't apply. I know I can return an Object and the other solution is to use query with NEW and create my own object (no #entity, like a pojo). But is there a way that I can do this using jpa, to be able to use repository and services, if I am creating my own pojo then i will create a #transactional class put the queries (with NEW) there and this is it. I don't like this approach and I don't think that the jpa does't allow you to select only some columns, but I didn't find a proper way.
Maybe you will ask what is the result if I am doing like this:
I get this error: "Cannot create TypedQuery for query with more than one return using requested result type [java.lang.Long]"
(For new queries, I am talking about : http://www.java2s.com/Tutorials/Java/JPA/4800__JPA_Query_new_Object.htm maybe I was not clear)
You can do the same by using below approach.
Just create a constructor in entity class with all the required parameters and then in jpa query use new operator in query like below.
String query = "SELECT NEW com.dt.es.CustomObject(p.uniquePID) FROM PatientRegistration AS p";
TypedQuery<CustomObject> typedQuery = entityManager().createQuery(query , CustomObject.class);
List<CustomObject> results = typedQuery.getResultList();
return results;
And CustomObject class should look like below with the constructor.
public class CustomObject {
private String uniquePID;
public CustomObject(String uniquePID) {
super();
this.uniquePID = uniquePID;
}
public String getUniquePID() {
return uniquePID;
}
public void setUniquePID(String uniquePID) {
this.uniquePID = uniquePID;
}
}
spring-data-jpa projection not need to map the whole table, just select the necessary fileds :
// define the dto interface
public interface CatDto {
String getName();
// other necessary fields
...
}
#Query(value = "select c.name as name, ... from Cat as c ...)
Page<CatDto> getAllCats(Pageable pageable);
By this way, CatDto is an interface and it only includes some fileds part of the whole table. Its fields name need to match the select field's alias name.

Qualified OneToMany-ManyToOne relation

i try to build a sample qualified relation and get a null value for the qualifier column in the join table.
Error:
Internal Exception: java.sql.SQLIntegrityConstraintViolationException:
cannot insert NULL into 'TIRE_POSITION'
Entities:
#Entity
public class Bike {
#OneToMany(mappedBy = "bike", cascade=CascadeType.ALL)
#MapKeyColumn(name="TIRE_POSITION", nullable=false)
private Map<String, Tire> tires;
...
}
#Entity
public class Tire {
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="BIKE_ID")
private Bike bike;
public enum TirePosition{
FRONT("FRONT"), BACK("BACK");
...
}
}
Insert invocation:
public void testIt(){
Bike bike = new Bike();
Tire tireFront = new Tire();
Tire tireBack = new Tire();
Map<String, Tire> tires = new HashMap<String, Tire>();
tires.put(Tire.TirePosition.BACK.getPosition(), tireBack);
tires.put(Tire.TirePosition.FRONT.getPosition(), tireFront);
bike.setTires(tires);
EntityTransaction trans = em.getTransaction();
trans.begin();
em.persist(bike);
trans.commit();
}
Anyone an idea what is wrong here?
Set both sides of your relationship and also remove the nullable=false constraint to see if the provider ends up setting the field. As JB Nizet pointed out, you are using a JoinColumn relationship instead of a relation table, which will force the "TIRE_POSITION" to be put in the Tire table; Some providers (EclipseLink) will insert the Tire table with all the data from the Tire entity and then later on update the reference with mapping data contained in other entities. Since the "TIRE_POSITION" field is mapped through Bike, it will get updated later. If so, you can either remove the database constraint or set your database to delay constraint processing until after the transaction.

Define a variable in Entity class which is not a column

In my WAS application, I have a requirement to define a variable(String) in an Entity class that maps to a table.
So the fields that are related to the table have annotation as #Column(name="column_name")
I have a requirement to add a new field/variable to this Entity class that is not a column in table. If I declare it as a normal field, JPA converts this field also in the SQLs. This is causing SQL -206 error(as expected).
How to do I declare this field? Is there an annotation that I can use to say its a custom variable and not related to any of the columns in the table defined by this Entity?
example:
#Entity
#Table(name = "TABLE_1")
#NamedQueries(
{
#NamedQuery(name = "abc:findTableContent", query = "SELECT u FROM TABLE_1 u WHERE u.Id = :iD"),
})
public class TableEntity1 implements Serializable
{
#Id
#Column(name = "TABLE1_ID")
private Long iD;
#Column(name = "DESC")
private String desc;
private String error;
}
So if I run the namedquery, it gets executed as "SELECT t0.iD, t0.desc, t0.error FROM TABLE_1 u WHERE u.iD=?"
How do I solve this? Thanks for your help!
I found the answer. I could mark the field or variable as #javax.persistence.Transient

JPA: JOIN in JPQL

I thought I know how to use JOIN in JPQL but apparently not. Can anyone help me?
select b.fname, b.lname from Users b JOIN Groups c where c.groupName = :groupName
This give me Exception
org.eclipse.persistence.exceptions.JPQLException
Exception Description: Syntax error parsing the query
Internal Exception: org.eclipse.persistence.internal.libraries.antlr.runtime.EarlyExitException
Users have a OneToMany relationship with Groups.
Users.java
#Entity
public class Users implements Serializable{
#OneToMany(mappedBy="user", cascade=CascadeType.ALL)
List<Groups> groups = null;
}
Groups.java
#Entity
public class Groups implements Serializable {
#ManyToOne
#JoinColumn(name="USERID")
private Users user;
}
My second question is let say this query return a unique result, then if I do
String temp = (String) em.createNamedQuery("***")
.setParameter("groupName", groupName)
.getSingleResult();
*** represent the query name above. So does fname and lname concatenated together inside temp or I get a List<String> back?
Join on one-to-many relation in JPQL looks as follows:
select b.fname, b.lname from Users b JOIN b.groups c where c.groupName = :groupName
When several properties are specified in select clause, result is returned as Object[]:
Object[] temp = (Object[]) em.createNamedQuery("...")
.setParameter("groupName", groupName)
.getSingleResult();
String fname = (String) temp[0];
String lname = (String) temp[1];
By the way, why your entities are named in plural form, it's confusing. If you want to have table names in plural, you may use #Table to specify the table name for the entity explicitly, so it doesn't interfere with reserved words:
#Entity #Table(name = "Users")
public class User implements Serializable { ... }

Categories