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);
Related
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
)
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 entity class is something like this:
#Entity
public class Book {
...
#ElementCollection
private List<String> authors;
...
}
Now I want query books by inputting an author's name, e.g. Tom, but ignore the name's case so tom/tOm/... should also match. I know that I can achieve using a collection member declaration:
select b from Book b, in(b.authors) a where lower(a) = 'tom'
However, in our application framework all JPQL statements are generated from a fixed template:
select b from Book b where WHERE_CLAUSE
I'm only allowed to provide the where clause. Anybody know how to get the same results as above with this template?
Finally I found a cheating way:
select b from Book b where (b.id in (select bb from Book bb, in(bb.authors) a where lower(a) = 'tom'))
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?