Join two tables with Play Framework and JPA - java

How can I join two tables by using java play framework and jpa, I really have a hardtime converting my MySQL query to jpa query.
Here is the MySQL query that I used in my old Java code:
SELECT * FROM tbl_majors
INNER JOIN tbl_lookup_user_major
ON tbl_majors.id=tbl_lookup_user_major.majorId
WHERE tbl_lookup_user_major.userId=12
//Table 1:
#Entity
#Table(name="tbl_majors")
public class Major extends Model {
public Major(){
}
#Column(name="major_name")
private String name;
#Column(name="major_desc")
private String description;
}
//Table 2
#Entity
#Table(name="tbl_lookup_user_major")
public class LookupUserMajor extends Model {
public LookupUserMajor(){
}
private int majorId;
private int userId;
}

Dont know if I get the exact point here, but in the tutorial blog "YABE", this kind of join table is used and created automatically by Play :
http://www.playframework.org/documentation/1.2.4/guide6#tagging
The many-to-many relation is described in the Model (between "Post" and "Tag" here for the blog sample) :
#ManyToMany(cascade=CascadeType.PERSIST)
public Set<Tag> tags;
public Post(User author, String title, String content) {
...
this.tags = new TreeSet<Tag>();
...
this.title = title;
this.content = content;
...
}
The YAML for the Posts data is :
Post(jeffPost):
title: The MVC application
postedAt: 2009-06-06
author: jeff
tags:
- play
- architecture
- mvc
After running the app, I check the database and the table "post_tag" is automatically created and all the links between the two tables are done (post_ids and tags_ids are filled).
Retrieving data seems as easy as :
"select distinct p from Post p join p.tags as t"
Can someone confirm that ? Because new to Java and JPA and Play ^^
If this is correct, it looks easier than managing the join table "manually".

Every time you have a field names "xxxId" in an entity, and "xxxId" is the ID of another entity, you did something wrong. The point of JPA is to manipulate objects, and associations between objects using object references or object collections.
Your tbl_lookup_user_major looks like a join table to me. Such a join table means that you have a many-to-many (or one-to-many, is one of the IDs is unique) between Major and User. So, your Major entity should have the following field :
#ManyToMany
#JoinTable(...) // details omitted
private Set<User> users;
And your JPA query should look like
select m from Major m
inner join m.users user
where user.id = :userId

Example Jpa query try like this...
Query query = JPA.em().createQuery(" SELECT * FROM "+User.class.getName() +" AS a JOIN "+
Role.class.getName()+" AS b WHERE a.roleId=b.roleId ");

Related

Select only specific columns from joined tables (Many-to-Many) in Spring Data JPA

The purpose is to select columns from joined tables (Many-to-Many).
The problem i have is to select two columns from a joined Many-to-Many table.
I'am using Springboot 2.3 and Spring data Jpa.
I have this data model, and what i want to fetch are the blue boxed fields
So the native query could look like this (if i am right ...)
SELECT bg.id, bg.name, p.name, c.name, c.short_desc FROM boardgame as bg
JOIN boardgame_category bgc on bg.id = bgc.fk_game
JOIN publisher p on bg.fk_publisher = p.id
JOIN category c on bgc.fk_category = c.id
WHERE bg.id = :id
I first tried to work with dto in JPQL statment
public class BoardgameDto {
private long id;
private String name;
private String publisherName;
private Set<CatregoryDto> categoryDto;
// setter, getter etc...
}
public class CategoryDto {
private String name;
private String shortDesc;
// setter, getter etc...
}
The JQPL query could look like this , but it doesn't work (IDE shows errors on CategoryDto)
/* THIS DOESN'T WORK */
SELECT new org.moto.tryingstuff.dto.BoardgameDto(bg.id, bg.name, p.name,
new org.moto.tryingstuff.dto.CategoryDto(c.name, c.short_desc)) FROM Boardgame as bg, Publisher as p, Category as c
Well, I think the problem I have with this way of doing is that the dto's contructor can't receive a collection as written here, and i think neither another contructor in parameter.
Then i started looking at Criteria Queries, especialy multiselect, Tuple, Dto, but it look like i had same kind of problems so i didn't dive deeper into it.
Finally i used a JpaRepository and it's findById() method like this
public interface BoardgameRepository extends JpaRepository<Boardgame, Long> {
}
// In a test or service method
Boardgame game = repository.findById(long id);
Then i filter the fields i need to keep through mappings in Service or Controller layer. So the front only received need datas.
But it feel a bit overkill,
Am I missing something, any part of the framework that would allow me to select only specific columns?
As you wrote, you can't use a collection as the parameter of a constructor expression. That's because the expression gets applied to each record in the result set. These records are a flat data structure. They don't contain any collections. Your database returns a new record for each element in that collection instead.
But your constructor expression fails for a different reason. You're trying to combine 2 constructor expressions, and that's not supported. You need to remove the 2nd expression and perform that operation within the constructor of your DTO.
So, your query should look like this:
SELECT new org.moto.tryingstuff.dto.BoardgameDto(bg.id, bg.name, p.name, c.name, c.short_desc) FROM Boardgame as bg <Your JOIN CLAUSES HERE>
And the constructor of your BoardgameDto like this:
public class BoardgameDto {
public BoardgameDto(Long id, String gameName, String publisherName, String categoryName, String description) {
this.id = id;
this.name = gameName;
this.publisherName = publisherName;
this.category = new Category(categoryName, description);
}
...
}

How to code query from SQL in Java for object oriented metamodel

I'm trying to learn how to create an object oriented model database using a tool called: Eyedb (on linux). I've made some classes and records generators for this database but I don't know how to code queries for it using Java.
The structure of the database looks like this:
class Lecturer {
attribute string<32> name;
attribute strin<32> surname;
relationship set <Lecture*> lectures inverse Lecture::Lecturer;
};
class Item{
attribute string<32> name;
attribute string<512> description;
};
class Topic extends Item {
relationship set<Lecture*> tlecture inverse Lecture::Topic;
};
class Lecture {
relationship Lecturer *llecturer inverse Lecturer::lectures;
relationship Topic *ltopic inverse Topic::tlecture;
relationship set <Content*> lcontent inverse Content::Lecture;
relationship set <Equipment*> lequipment inverse Content::Lecture;
relationship Room_timeslot* lroom_timeslot inverse Room_timeslot::rlecture;
};
class Content extends Item {
attribute int level;
relationship set <Content*> subcontent inverse Content::supcontent;
relationship set <Content*> supcontent inverse Content::subcontent;
relationship set <Teachingmaterial*> cteachingmaterial inverse Teachingmaterial::Content;
};
class Teachingmaterial extends Item {
attribute string link;
relationship set <Content*> tcontent inverse Content::Teachingmaterial;
};
class Equipment extends Item {
attribute int quantity;
attribute string symbol;
relationship set<Lecture*> electure inverse Lecture::Equipment;
relationship Room *eroom inverse Room::requipment;
};
class Room {
relationship set <Equipment*> requipment inverse Equipment::Room
attribute string name;
attribute int number;
attribute string symbol;
attribute string building;
attribute int floor;
attribute string wing;
relationship set <Room_timeslot*> rroom_timeslot inverse Room_timeslot::Room;
};
class Room_timeslot {
relationship Room *room inverse room::rroom_timeslot;
relationship Lecture* rlecture inverse Lecture::Room_timeslot;
relationship set <Timeslot*> rtimeslot inverse Timeslot::lroom_timeslot;
};
class Timeslot {
attribute string name;
attribute int number;
attribute time timemargin_start;
attribute time timemargin_end;
relationship set <Room_timeslot*> troom_timeslot inverse Room_timeslot::rtimeslot;
};
I want to make queries that look like this:
SELECT lr.name as lecturer, t.name as topic FROM topic t
JOIN lecture l ON t.id_topic = l.id_topic
JOIN lecturer lr ON lr.id_lecturer = l.id_lecturer
ORDER BY lr.name
select t.name as topic from topic t
join lecture l on t.id_topic = l.id_topic
join lecturer lr on lr.id_lecturer = l.id_lecturer
where lr.name = "name1"
SELECT tm.name as teaching_material, e.name as equipment, lr.name as lecturer FROM teachingmaterial tm
JOIN teaching_content tc ON tc.id_teachingmaterial = tm.id_teachingmaterial
JOIN content c ON c.id_content = tc.id_content
JOIN content_lecture cl ON cl.id_content = c.id_content
JOIN lecture l ON l.id_lecture = cl.id_lecture
JOIN lecture_equipment le ON le.id_lecture = l.id_lecture
JOIN equipment e ON e.id_equipment = le.id_equipment
JOIN lecturer lr ON lr.id_lecturer = l.id_lecturer
Java uses JDBC to communicate with a relational database. If your database has a JDBC driver you're all set. If not, you're out of luck. Both SQL Server and MySQL have JDBC drivers, so they should not be a problem. You'll need those and the JDBC tutorial.
Looks like EyeDB is an object database that I've never heard of. It claims to be usable by C++ and Java.
I'd expect at least one "hello world" connection example. I'm suspicious of any software that doesn't even get the HTML on its web page right.
There's Java documentation available. Looks awful. Is this a requirement? There are better databases, both relational (MySQL, SQLLite, or PostgreSQL), object (e.g. JODB), or graph (NEO4J). Must you use this one?
The EyeDB documentation makes the steps clear. They are spelled out for you in the sample problem:
Define the schema using the EyeDB Object Definition Language (ODL)
Generate the Java classes from the schema using the EyeDB eyedbodl tool, which comes with your download.
Write a client program using the generated classes that interact with the database.
It's all there. Give it a try.

JPA support for outer joins without object relational mapping

Recently i had to use an outer join between two JPA entities that doesnt have object relational mapping. Going by the spec and the forum posts, outer joins are supported only if the entities are mapped at JPA level.
Example code below. Requirement is to find customers without any orders.
#Entity
class Customer {
private int id;
}
#Entity
class Order {
private int customerId;
public int getCustomerId() { return customerId; }
public void setCustomerId(int customerId) { this.customerId = customerId ; }
}
In my case, i had to opt for the native queries to get the job done.
Any thoughts on if future JPA specs going to support outer joins without relational mapping ?
Thanks
Rakesh
You can use a theta-style join to emulate an INNER JOIN:
select c, o
from Customer c, Order o
where c.id= o.customerId
Most modern database engine query optimizers will turn it into an INNER JOIN equivalent anyway.
Suppose you have customerId field (Integer) in the Order entity. In order to find customers without any orders, you can avoid outer join and native query by using subquery:
select c from Customer c where id not in (select customerId from Order)
This way you achieve the same goal in JPQL (HQL).

Join tables in Hibernate

I have two tables in my PostgreSQL database:
CREATE TABLE tableOne (id int, name varchar(10), address varchar(20))
CREATE TABLE tableTwo (id int, info text, addresses varchar(20)[])
now I want to create a join as follows:
SELECT * FROM tableOne JOIN tableTwo ON address = ANY(addresses)
I tried to achieve this using Hibernate - class TableOne:
#Entity
#Table(name = "tableOne")
class TableOne {
private int id;
private TableTwo tableTwo;
private String address;
#Id
#Column(name = "id")
public getId() { return id; }
#ManyToOne
#JoinFormula(value = "address = any(tableTwo.addresses)",
referencedColumnName = "addresses")
public TableTwo getTableTwo(){
return tableTwo;
}
// Setters follow here
}
But Hibernate keeps generating queries with non-sense JOIN clauses, like:
... JOIN tableTwo ON _this.address = any(tableTwo.addresses) = tableTwo.addresses
How do I tell Hibernate using annotations to format my join query correctly? Unfortunately, our project must be restricted only to the Criteria API.
EDIT:
After suggestion from ashokhein in the comments below, I annotated the method getTableTwo() with just #ManyToOne - and now I would like to do the join using Criteria API, presumably with createAlias(associationPath,alias,joinType,withClause) method where withClause would be my ON clause in the join.
But Im not sure what to put as associationPath and alias parameters.
Any hints?
To support PostgreSQL array you need a custom Hibernate Type. Having a dedicated user type will allow you to run native SQL queries to make use of the type:
String[] values = ...
Type arrayType = new CustomType(new ArrayUserType());
query.setParameter("value", values, arrayType);
HQL supports ANY/SOME syntax but only for sub-queries. In your case you'll need a native query to use the PostgreSQL specific ANY clause against array values.
You can try Named Query.
#NamedQuery(name="JOINQUERY", query="SELECT one FROM tableOne one JOIN tableTwo two ON one.address = :address" )
#Entity
class TableOne{......
Retrieving part is:
TypedQuery<TableOne> q = em.createNamedQuery("query", TableOne.class);
q.setParameter("address", "Mumbai");
for (TableOne t : q.getResultList())
System.out.println(t.address);
You might need to do some permutations on the query
So after a lot of time searching for the right answer, the only real solution that works for us is creating a view:
CREATE VIEW TableA_view AS SELECT TableOne.*,TableTwo.id FROM TableA JOIN TableTwo ON TableOne.address = ANY(TableTwo.addresses)
and mapping it to an entity TableOne instead of the original table.
This was the only solution for us besides, of course, using a named query, which was a no-go as we needed to stick to the Criteria API.
As #ericbn has mentioned in the comments this is really an example where ORM gets really annoying. I would never expect that custom join clause like this is not possible to do in Hibernate.
#JoinFormula should contain SQL instead of HQL.
https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/annotations/JoinFormula.html

JPA : Is there any way to run a simple SELECT statement that only access a few columns?

I'm new to JPA so forgive me if my question seems silly.
We have used JPA in our project. I see that every entity object has a direct mapping with a table and each row in the table is an object of that entity type.
But, suppose I only want to access one or two columns of a table, how do i go about doing it ? The reason I'm asking is because of the task i have in hand.
There are two tables. The first table has everything set up with JPA so that each row can be cast into an object type. The first table has a column that is referenced in the second table i.e. say, table A has column CLOTH_ID and Table B has columns CLOTH_ID and CLOTH_DESCRIPTION. CLOTH_ID is used in both Table A and B; But B has the CLOTH_DESCRIPTION columns which corresponds to CLOTH_ID.
I'm displaying Table A in my webpage but I also need to display : CLOTH_DESCRIPTION in my webpage. Is there a JPA oriented way to do this or Am i better off using regular JDBC to extract the CLOTH DESCRIPTION values ?
I assume you have the following setup:
#Entity
#Table(name="A")
class A {
#ManyToOne
#JoinColumn(name="CLOTH_ID")
private B cloth;
//...
}
#Entity
#Table(name="B")
class B {
#Id
#Column(name="CLOTH_ID")
private int id;
#Column(name="CLOTH_DESCRIPTION")
private String description;
//...
}
If you don't... you're doing it wrong (i.e. it is not idiomatic JPA usage). You have the following options:
Simply fetch A
In this case #ManyToOne relationship will be fetched eagerly by default as well. Then simply call in Java:
a.getCloth().getDescription()
Prefer this approach as it is the simplest and most idiomatic unless the number of columns in B is huge.
Use JPA query with custom columns:
SELECT a, a.b.description
FROM A a
WHERE a.id = :id
In this case the query returns List<Object[]>, where Object[] actually contains two elements: A and String.
Same as above but with custom DTO:
class Adto {
private final A a;
private final String description;
public Adto(A a, String description) {
this.a = a;
this.description = description;
}
}
And slightly modified query:
SELECT new Adto(a, a.b.description)
FROM A a
WHERE a.id = :id

Categories

Resources