In my software, I have an entity (let's call it Member) with a collection of another entity (let's call it State). The query I need to write should return all members who have no State with a specific property value (e. g. 5).
Here are the relevant parts of the entities:
public class Member {
#JoinColumn(name = "MEMBER_ID")
#OneToMany
private List<State> states;
#Column
private String name;
}
public class State {
#Column
private int property;
}
Note that there is no bidirectional mapping between Member and State, the mapping is declared on the non-owning side of the relation (Member). In SQL I would create a query like this:
SELECT m.name
FROM Member m
WHERE NOT EXISTS (
SELECT *
FROM State s
WHERE
m.id = s.member_id
AND s.property = 5
);
But I don't know of any way to achieve the same thing in JPQL without having a mapping on the owning side of the relation. Is there any way to achieve this without having to bother with bidirectional mappings?
JPA allows to use collection references in subquery from clauses, so you can use this:
SELECT m.name
FROM Member m
WHERE NOT EXISTS(
SELECT 1
FROM m.states s
WHERE s.property = 5
)
This will produce exactly the SQL you want.
you can write native query like this
select name from Member where member_id not in ( select id from states
where property = 5)
Try something like this
select m.name from Member m where not exists(
from Member m1 join m1.states s where s.property = 5
)
or
select m.name from Member m where m.id not in(
select m1.id from Member m1 join m1.states s where s.property = 5
)
Related
I have a question about Mappers in Mybatis. I have two classes like this:
public class A{
private String A1;
private String A2;
private List<B> listB;
//getters and setters
.
.
.
public static class B {
private String B1;
private String B2;
//getters and setters
.
.
.
}
}
Then I have a mapper class like this:
#Mapper
public interface ABMapper{
#Select("select b1,b2 from b where b.a1 = #{a1}")
public List<B> getBs(#Param("a1") String a1);
#Select ("select a1,a2 from a limit 100")
#Results({
#Result(property="a1", value = "a1"),
#Result(property="a2", value = "a2"),
#Result(property="listB", column="a1", many = #Many(select = "getBs"))
})
public List<A> getAs();
}
This works fine, but I know that when getAs() executes, getBs runs as many times as items have (limit 100 is an example).
I wonder that if exists a way to run a query like select a.a1,a.a2,b.b1,b.b2 from a a inner join b b on a.a1 = b.a1 and then Mybatis (and Java) could group elements in List<A> and the attribute B is not empty.
Perhaps, it necessary to use hash and equals in class A and B, but I don't know.
Thanks for your answers.
Mybatis can do that but only if you use xml mapping. The limitation of annotations in java makes it impossible to map associations with join:
You will notice that join mapping is not supported via the Annotations API. This is due to the limitation in Java Annotations that does not allow for circular references.
In this case the mapping might look like:
<resultMap id="bMap" type="B">
<id property="b1" column="b1"/>
<result property="b2" column="b2"/>
</resultMap>
<resultMap id="aMap" type="A">
<id property="a1" column="a1"/>
<result property="a2" column="a2"/>
<collection property="listB" javaType="B" resultMap="bMap" columnPrefix="b_"/>
</resultMap>
<select id='getAs' resultMap='aMap'>
SELECT a.*, b.id B_id, b.b1 B_b1, b.b2 B_b2
FROM (
select *
from a
LIMIT 100
) AS a
LEFT JOIN AS b on a.a1 = b.a1
</select>
Some important notes:
both A and B should have some identifying field(s) configured with id element. The value in this field would be used to identify object and do what you call grouping. For table a this seems to be a1 (as you used it as a join field) and I used it in the example.
autoMapping="true" in resultMap might be useful if there are many fields to be mapped
you need to use left/right join to handle those records from table a that do not have anything in b.
in order to LIMIT work correctly with join you need to do it on the select that gets records from a and not on the join result otherwise you may get less than 100 records in the result if more than 100 records from b are joined.
it depends on the use case but usually if you use LIMIT you need to specify some order, otherwise records will be returned in unpredictable order.
In the older versions of mybatis there was a bug that required that column prefixes in the query should be in upper case (may be this is fixed now, I'm not sure).
My requirement is say I have two table:
T1: id,name,email
T2: id,address
To get data from both table I have done like:
Collection ls=null;
EntityManager em=ConnectionUtils.getEntityManager();
tx= em.getTransaction();
tx.begin();
Query q=em.createQuery("select t1.name,t2.address from T1 t1, T2 t2");
ls=(List<T1T2>)q.getResultList();
OP:[[Ljava.lang.Object;#e836bd1, [Ljava.lang.Object;#561b6dc8,
[Ljava.lang.Object;#22c491a2, [Ljava.lang.Object;#17353483,
[Ljava.lang.Object;#260a905c, [Ljava.lang.Object;#7f8b9b86,
[Ljava.lang.Object;#268fbbd5, [Ljava.lang.Object;#2674b0ba,
[Ljava.lang.Object;#36fe970f, [Ljava.lang.Object;#46f75fe,
[Ljava.lang.Object;#31ab78f8, [Ljava.lang.Object;#7092fb41,
[Ljava.lang.Object;#41ada224, [Ljava.lang.Object;#6e700b2b]
ya its annoying.
I am getting data but its an normal Object.
I have created pojo as:
T1T2: String name;String address; to get returned object in this
format. but getting proper format instead Entity error and that pojo
is not error.
I want same type of concept as marshalling of json string to corresponding pojo.
If you want to use JPA for mapping to POJO's use #SqlResultSetMapping annotation
Assuming T1T2 has a constructor T1T2(String name, String address) add this to any of your entity class definition
#SqlResultSetMapping(name = "CUSTOM_MAPPING", classes = #ConstructorResult(
targetClass = T1T2.class,
columns = {#ColumnResult(name = "name", type = String.class),
#ColumnResult(name = "address", type = String.class)}))
Now you can use this mapping:
Query q=em.createNativeQuery("select t1.name,t2.address from T1 t1, T2 t2","CUSTOM_MAPPING");
List<T1T2> = q.getResultList();
Note that it work only on native queries. I'm assuming that there is no association defined at JPA entity level between T1 and T2, otherwise whole process is obsolete. If there is an association, use #JoinTable annotation to declare it and JPA will make sure to fetch association along with entity.
In case you don't need full ORM functionality (or do not know how to do it properly), you can accomplish your task with Spring's JdbcTemplate:
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
List<T1T2> pojos = jdbcTemplate.query(
"SELECT t1.name, t2.address FROM T1 t1 JOIN T2 t2 ON t1.id=t2.id",
new BeanPropertyRowMapper(T1T2.class));
BeanPropertyRowMapper maps result set columns to POJO fields that have the same name, respecting field types.
Mapping JPQL Query Result to POJO
When you execute the query
Query q=em.createQuery("select t1.name,t2.address from T1 t1, T2 t2");
Collection c = q.getResultList();
you'll get a collection of zero or more instances of arrays of type Object. If you want the result of the query be mapped to a POJO, you can use the so called Construcot Expression. To use it, first define your POJO as follows with an appropriate constructor:
package com.myproject.dto;
public class T1T2 {
private String name;
private String address;
public T1T2() {}
public T1T2(String name, String address) {
this.name = name;
this.address = address;
}
// getters + setters
}
Then you can formulate your query as follows:
String queryString = "SELECT NEW com.myproject.dto.T1T2(t1.name, t2.address) FROM T1 t1, T2 t2";
TypedQuery<T1T2> q = em.createQuery(queryString, T1T2.class);
Collection<T1T2> result = q.getResultList();
Now you should have a collection of zero or more POJO instances and you don't need to cast.
Here is an extract from the JPA 2.0 Spec if you want to understand the details:
4.8.2 Constructor Expressions in the SELECT Clause
A constructor may be used in the SELECT list to return an instance of a Java class. The specified class is not required to be an entity or to be mapped to the database. The constructor name must be fully qualified.
If an entity class name is specified as the constructor name in the SELECT NEW clause, the resulting entity instances are in the new state.
If a single_valued_path_expression or identification_variable that is an argument to the constructor references an entity, the resulting entity instance referenced by that single_valued_path_expression or identification_variable will be in the managed state.
Note: Your query is building a x-product, do you really want that? As of now, you'll get any name with any address.
I have a two tables: Person and Dog. You know that person may have more than one dog.
My model is:
public class {
int personId;
List <Dog> dogs;
String name;
String surname;
}
public class Dog{
String name;
int dogId;
}
When it comes to database it is fairly similar:
PersonID(PK), Name (String), surname(String)
dogId(PK), name(String), IdOwner(FK)
Can you help me write select in mybatis ? I tried to read about #one and #many.
If you are using MyBatis you have two options described in the reference documentation:
Nested Select: By executing another mapped SQL statement that returns the complex type desired.
Nested Results: By using nested result mappings to deal with repeating subsets of joined results.
In your case, as you want to load the many association you must use the Nested Select, because you can't load a many association using a fetch outer join (just associations with one row fetched)
Nested Select
In this option you should need to add a reference to the select, which loads the daya by the foreign key relationship (in your case foreign key of person), of the relationship which in your case is dogs in the `ResultMap.
So you should have a query which loads the Person table:
<select id="findById" resultMap="personResult">
SELECT * FROM PERSON WHERE NAME = #{name}
</select>
Its method:
Person findById(String name);
Then a query which loads the dogs by person key relationship:
<select id="findDogsByPerson" resultType="Dog">
SELECT * FROM DOG WHERE ID_PERSON = #{namePerson}
</select>
And its method:
List<Dog> findDogsByPerson(String namePerson);
Then you must add the select as association in the resultmap referencing to the select by foreign key (findDogsByPerson). In your case is a many association, so you should use the collection tag instead of association.
<resultMap id="personResult" type="Person">
<!--Other properties maps -->
<!-- ..... -->
<collection property="dogs" column="id_person" javaType="Dog" select="selectDogByPerson"/>
<!-- ..... -->
</resultMap>
Annotation alternative
If you want you can use annotations to do it. It's almost the same but the ResultMap and Select goes above the methods. It would using the anotation #Many referencing to the many relationship.
#Select("SELECT * FROM PERSON WHERE NAME = #{name}")
#Results(value = {
#Result(property="name", column="name"),
#Result(property="surname", column="surname"),
#Result(property="dogs", javaType=List.class, column="name",
many=#Many(select="findDogsByPerson"))})
Person findById(String name);
#Select("SELECT * FROM DOG WHERE ID_PERSON = #{namePerson}")
#Results(value = {
#Result(property="name", column="name"),
#Result(property="dogId", column="dogId")})
List<Dog> findDogsByPerson(String namePerson);
I have the following relation of three classes:
#Entity
public class User{
#OnetoMany
List<Attribute> attributes = new ArrayList<Attribute>();
}
#Entity
public class Attribute{
#ManyToOne
AttributeType attributeType;
}
#Entity
public class AttributeType{
#Column
String type;
}
One user can have n attributes of m types.
I need to create HQL query which will return all Atribute Types List<AttributeType> of specific user attributes.
For example user has attribute a of type t, atribute b of type t and attribute c of type t1.
I need to return List<AttributeType> which will contain t and t1.
Please help. I just got lost in this query.
You shall map Attribute to User many to one relation, so the following query is what you need:
select distinct atr.attributeType
from Attribute atr
where atr.user = :user
I think the following query will work too:
select distinct atrs.attributeType
from User as user
join user.attributes as atrs
where user.id = :user_id
I have the following problem. I have three classes, A, B and C. A contains a OneToMany relationed list of B:s. B contains a ManyToOne relation to C. C contains a field called "name" and B also contains a field called "name". What I'd like to accomplish is to have the items in A's list sorted primarily by C's name and secondarily by B's name - the problem is that I do not know how to do this. Is it even possible?
I'm using EclipseLink as my JPA provider.
class A {
#OneToMany
#OrderBy("b.c.name, b.name") <---- this is the problem
List<B> b;
}
class B {
#ManyToOne
C c;
String name;
}
class C {
String name;
}
EDIT
Yes, I've tried different variations, for example #OrderBy("c.name") doesn't work, I just get an error message telling me that the entity class b does not contain a field called "c.name".
It's NOT possible. #OrderBy only accepts direct property / field names, not nested properties. Which makes sense, really, because "c" table - depending on your fetching strategy may not even be part of a select issued to retrieve your "b"s.
ChssPly76 is right.
What you could do is to create a named query like this one:
SELECT b
FROM B b
WHERE b.a = :mya
ORDER BY b.c.name
Have you tried #OrderBy("c.name", "name") ?
You shouldn't use "b." because it's implied that the #OrderBy will be done on columns of the instances of B on the b array.
Have you tried:
#OrderBy("c.name ASC", "name ASC")
?
It is not possible in javax.persistence.OrderBy (as say ChssPly76 ), but when I was using Hibernate I construct new column in PLAIN SQL with Formula() annotation and then OrderBy over it:
class A {
#OneToMany
#OrderBy("orderCol") <---- reference to virtual column
List<B> b;
}
class B {
#ManyToOne
C c;
String name;
#org.hibernate.annotations.Formula(
"( select C_table.name as orderCol from C_table where C_table.id = id )"
) <------------------------------------------ join with plain SQL statment
String orderCol;
}
May be EclipseLink has same possibilities?