I am using java hibernate to store my data entities. I want to know the sql command to select the data in a #ManyToMany column using postgresql-psql command.
for normal columns, I can just run:
SELECT id FROM university;
But now I have the following university entity:
#Entity
#Table(name = "university")
public class University {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private Long id;
#ManyToMany(fetch=FetchType.EAGER)
#JoinColumn(name="students" /* referencedColumnName="id" */)
private List<Student> students;
}
#Entity
#Table(name = "student", uniqueConstraints = { #UniqueConstraint(columnNames={"name"})})
public class Student
{
#Id
private Long id;
#NotBlank
#NotNull
private String name;
}
The problem is, I don't know what the student list is called in psql.
When I run:
SELECT students FROM university;
I get:
ERROR: column "students" does not exist
When I type:
\d university
I get (not actual data: data anonymized to student/university example):
Column | Type | Collation | Nullable | Default
------------------+-----------------------------+-----------+----------+---------
id | bigint | | not null |
Indexes:
"university_pkey" PRIMARY KEY, btree (id)
"uk_rwpd2frv6wtkgqtxn3envk3i8" UNIQUE CONSTRAINT, btree (name)
Referenced by:
TABLE "university_students" CONSTRAINT "fkdkjk4jgutu64g937gkknybax2" FOREIGN KEY (university) REFERENCES university(id)
You have a table 'university_students', can you do 'select * from university_students'
I think you have this structure :
student:
id
name
0
first_student
university:
id
3
university_students:
university_id
students_id
3
0
So the only think you need to do is this :
SELECT * FROM student WHERE id IN (SELECT students_id FROM university_students WHERE university_id = 3)
That will search all students_id where university_id is equal to 3 in the table university_students, all that will match with the table student.
if you only want their name replace * by name like this :
SELECT name FROM student WHERE id IN (SELECT students_id FROM university_students WHERE university_id = 3)
OK got what I wanted, inspired by: https://stackoverflow.com/a/3486662/2396744
Given:
University:
database=> SELECT id FROM university;
id
----
2
3
4
5
(4 rows)
Students:
database=> SELECT id,name FROM students;
id | name
----+----------
4 | Jack
3 | Jill
2 | Jonas
(3 rows)
university_students:
database=> SELECT * FROM university_students;
university_id | students_id
---------------+---------
3 | 3
3 | 2
4 | 4
4 | 2
5 | 3
5 | 2
(6 rows)
The query becomes:
database=> SELECT u.id,us.university_id,us.students_id,s.name
FROM students as s, university as u, university_students as us
WHERE u.id = 5
AND us.university_id = u.id
AND us.student_id = s.id;
id | university_id | student_id | name
----+---------------+------------+----------
5 | 5 | 3 | Jill
5 | 5 | 2 | Jonas
(2 rows)
You can and you really should learn to use JpaRepository of java spring boot, it's easy to use and you can do lot of thing with it, for example i have this class : Client.java
#Entity
public class Client {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
#Column(unique = true, nullable = false) private String email, username;
#Column(unique = true) private String phone;
#Column(nullable = false) private String password, firstName, lastName;
private boolean active;
private long timestampCreation;
private double coefficientReducingBuyer;
private long score;
// User privileges, roles, Address
#ManyToMany(mappedBy = "clients", fetch = FetchType.EAGER) private Set<Privilege> privileges;
#ManyToOne(fetch = FetchType.EAGER) private Role role;
#OneToMany(mappedBy = "client", fetch = FetchType.EAGER, cascade = CascadeType.ALL) private Set<AddressClient> addressClients;
// Market
#OneToMany(fetch = FetchType.EAGER) private Set<Order> orders;
#OneToOne(mappedBy = "client", cascade = CascadeType.ALL) private ShoppingCart shopping;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) private Set<Favorite> favorites;
}
public interface ClientRepository extends JpaRepository<Client, Integer> {
// Find all client by phone number
Client findByPhone(String phone);
// Find all client by phone number which is containing some element of my string
List<Client> findByPhoneIsContaining(String phone);
// Same but ignoring capital letter a and A is the same here
List<Client> findByPhoneIgnoreCaseIsContaining(String phone);
// Find by other table
List<Client> findByRole(Role role);
// Find by other table with a specific value in it
Set<Client> findByPrivileges_Name(String privilege);
}
You don't need to add #Table, only if you want to change the name of your table.
Here my table will name client but i can change is name to user when i do this : #Table(name="user"), so don't use #Table when you don't need it.
Related
I had a db with tables SPEC and PARTS.Also I had a table for MANY TO MANY relations. In my project I used spring jdbs template and all works good. Then I decide to change jdbc on SPring data jpa.
My Entities:
#Entity
#Table(name = "PARTS")
public class PartsJpa {
#Id
private int id;
#Column(name = "NAME")
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "ID_EXPORT", unique = false, nullable = false, updatable = true)
private ExportJpa exportJpa;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "ID_TYPE", unique = false, nullable = false, updatable = true)
private TypesJpa typesJpa;
#Column(name = "DESCRIPTION")
private String description;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name="SPEC_PARTS",
joinColumns = #JoinColumn(name="ID_SPEC", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="ID_PARTS", referencedColumnName="id")
)
private Set<SpecJpa> specJpa;
////////
}
And Spec:
#Entity
#Table(name = "SPEC")
public class SpecJpa {
#Id
private int id;
#Column(name = "NAME")
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "Creator_ID", unique = false, nullable = false, updatable = true)
private UsersJpa usersJpa;
#Column(name = "DESCRIPTION")
private String description;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name="SPEC_PARTS",
joinColumns = #JoinColumn(name="ID_SPEC", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="ID_PARTS", referencedColumnName="id")
)
private Set<PartsJpa> partsJpa;
////////////////
}
I don't show getters and setters.
It works, but when I start a programm, something in my table was changed and now I can't add in table spec_parts values like(1,3)(1,2).
Mistake:
FK_123: PUBLIC.SPEC_PARTS FOREIGN KEY(ID_PARTS) REFERENCES PUBLIC.SPEC(ID) (3)" Referential integrity constraint violation: "FK_123: PUBLIC.SPEC_PARTS FOREIGN KEY(ID_PARTS) REFERENCES PUBLIC.SPEC(ID) (3)"; SQL statement: INSERT INTO "PUBLIC"."SPEC_PARTS"("ID_SPEC","ID_PARTS")VALUES(?,?)
Maybe I have mistake with creating relations between spec and parts? What problem it can be?
data in spec
ID NAME CREATOR_ID DESCRIPTION CHANGER_ID
1 pc 1 description 1
2 pc2 2 description2 2
data in parts
ID ▼ NAME ID_EXPORT ID_TYPE DESCRIPTION
1 intel core i5 1 1 d1
2 intel core i7 1 1 d2
3 ddr3 2 2 d3
4 ddr4 2 2 d4
5 asus 3 3 d5
data in spec_parts now:
ID_SPEC ID_PARTS
1 1
2 2
so I can't add 1,3 or 2,4
I find a problem, spring date change something and now in table SPEC_PARTS ID_SPEC mapping on PARTS.ID. Why?
As you are using ManyToMany relation, there is a mapping table created named SPEC_PARTS which have referenced columns ID_SPEC and ID_PARTS.These columns value come from SPEC.ID and PARTS.ID. So you can't insert in SPEC_PARTS without creating referenced value because you are trying to do foreign key constraint violation.
Suppose if there is a row in SPEC with id value 1 and there is a row in PARTS with id value 2. Then you can insert in SPEC_PARTS with value like (1,2).
So, first, add data in SPEC and PARTS then map them in SPEC_PARTS.
And you can remove #JoinTable from one side, you don't need to define it both side.
Update:
Problem is SpecJpa class relation. Here you are using SPEC_PARTS.ID_SPEC as foriegn key for PARTS.ID and SPEC_PARTS.ID_PARTS as foriegn key for SPEC.ID which is fully reversed what you do in PartsJpa class.
#JoinTable(name="SPEC_PARTS",
joinColumns = #JoinColumn(name="ID_SPEC", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="ID_PARTS", referencedColumnName="id")
)
That's why this error say
SPEC_PARTS FOREIGN KEY(ID_PARTS) REFERENCES PUBLIC.SPEC(ID) (3)";
There is no SPEC.ID value 3 exist in the database.
Solution:
Remove #JoinTable from SpecJpa class as you don't need to specify both side.
And remove the wrong relation of the foreign key from database also.
I am using spring with hibernate to store data in MySql database. I am trying to retrieve rows based on filters requested by the user.
I have the following tables/entities : Product and Gemstone
Relations:
Product many2many Gemstone
I am trying to write a query to get products that have Gemstone A and Gemstone B and Gemstone C.. and so on.
Use Case:
If user is asking for a product with gemstones 51 and 46. Query should only return product id 4.
Query:
filterGemstones() method return the gemstone user wants to filter products to. Using the below query I get zero records but if I remove HAVING Count(DISTINCT p.product_id) = 2 I get product id 4, 5
HQL :
createQuery("select p.productId from Product p JOIN p.gemstones g where g in :gemstones group by p having count (distinct p) =" + filterGemstones().size() ).setParameter("gemstones",filterGemstones());
SQL generate by hibernate :
SELECT p.product_id
FROM product p
INNER JOIN gemstone_product gp
ON p.product_id = gp.product_id
INNER JOIN gemstone g
ON gp.gemstone_id = g.gemstone_id
WHERE g.gemstone_id IN ( 51, 46 )
GROUP BY p.product_id
HAVING Count(DISTINCT p.product_id) = 2
Product class:
#Entity
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "product_id")
private long productId;
#ManyToMany()
#JoinTable(
name = "gemstone_product",
joinColumns = {#JoinColumn(name = "product_id")},
inverseJoinColumns = {#JoinColumn(name = "gemstone_id")}
)
private Set<Gemstone> gemstones = new HashSet<>(0);
// setters and getters
}
Gemstone class:
#Entity
#Table(name = "gemstone")
public class Gemstone {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "gemstone_id")
private long gemstoneId;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "gemstone_product",
joinColumns = {#JoinColumn(name = "gemstone_id")},
inverseJoinColumns = {#JoinColumn(name = "product_id")}
)
private Set<Product> products = new HashSet<>(0);
// setters and getters
}
Actually the SQL query that we need here is pretty simple:
SELECT t1.product_id
FROM gemstone_product AS t1
WHERE (t1.gemstone_id IN ?1 ) # (51, 46)
GROUP BY t1.product_id
HAVING (COUNT(t1.gemstone_id) = ?2) # 2 - # of items
It's a bit frustrating that it's not easy to create it with JPA, but it can be done with FluentJPA (produces the query above):
public List<Integer> getProductsContainingAllStones(List<Long> gemstoneIds) {
int count = gemstoneIds.size();
FluentQuery query = FluentJPA.SQL((Gemstone gemstone,
JoinTable<Gemstone, Product> gemstoneProduct) -> {
discardSQL(gemstoneProduct.join(gemstone, Gemstone::getProducts));
long productId = gemstoneProduct.getInverseJoined().getProductId();
long gemstoneId = gemstoneProduct.getJoined().getGemstoneId();
SELECT(productId);
FROM(gemstoneProduct);
WHERE(gemstoneIds.contains(gemstoneId));
GROUP(BY(productId));
HAVING(COUNT(gemstoneId) == count);
});
return query.createQuery(em).getResultList();
}
More details on how it works can be found here.
I have two tables - user and booking. Each user may have many bookings (one-to-many relationship).
user: booking:
id | name | id | country | user_id | price |
-------------| ------------------------------------|
1 | Alice | 1 | Italy | 1 | 2000 |
2 | Bob | 2 | France | 1 | 2500 |
3 | Spain | 1 | 3000 |
I want to select all users and all bookings where booking's price is greater than 2000 using Query DSL. If a user doesn't have any bookings or bookings don't match the condition I still want to select this user.
First, let's have a look at how it would look like using a simple SQL left join query:
SELECT u.*, b.* FROM user u LEFT JOIN booking b ON u.id = b.user_id AND b.price > 2000
The above query should provide the following result:
id | name | id | country | user_id | price |
-------------|----------------------------------------|
1 | Alice | 2 | France | 1 | 2500 |
1 | Alice | 3 | Spain | 1 | 3000 |
2 | Bob | null | null | null | null |
Now I want to do it using JPA with Query DSL
JPA-related stuff:
#Entity
public class User {
#Id
private Long id;
private String name;
#OneToMany(cascade = ALL, fetch = EAGER, orphanRemoval = true, mappedBy = "user")
private List<Booking> bookings;
// getters and setters
}
#Entity
public class Booking {
#Id
private Long id;
private String name;
private Integer price;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "user_id")
private User user;
// getters and setters
}
Query DSL:
public List<User> getUsersAndBookings() {
QUser user = QUser.user;
QBooking booking = QBooking.booking;
JPAQuery<User> jpaQuery = new JPAQuery(entityManager);
List<User> result = jpaQuery.from(user).leftJoin(user.bookings, booking).on(booking.price.gt(2000)).fetchJoin().fetch();
return result;
}
In fact, this code is not working and I get the following exception:
org.hibernate.hql.internal.ast.QuerySyntaxException: with-clause not allowed on fetched associations; use filters [select user from com.example.demo.entity.User user left join fetch user.bookings as booking with booking.price > ?1]
The problem is that the condition clause is specified in on method - on(booking.price.gt(2000)).
After some research I found that this condition should be specified in where method and should look like this:
List<User> result = jpaQuery.from(user).leftJoin(user.bookings, booking).where(booking.price.gt(2000)).fetchJoin().fetch();
This works, but not how I would expect it to work, since it doesn't return ALL users, it returns only one user (Alice), which has some bookings, matching the condition clause. Basically, it just filters the merged table (result table after left join operation) and that's not what I'm looking for.
I want to retrieve all users, and if there are no any bookings for a specific user, then just have null instead of booking list for this user.
Please help, been struggling for hours without any success.
Versions used:
Spring Boot 2.0.2
Spring Data JPA 2.0.7
Hibernate 5.2.16.Final
QueryDSL 4.1.4
You can use isNull expression in where clause to get the rows that have null values.
Your query should be like this:
jpaQuery.from(user)
.leftJoin(user.bookings, booking)
.fetchJoin()
.where(booking.price.gt(2000).or(booking.id.isNull())).fetch();
Hibernate produced query:
select
user0_.id as id1_1_0_,
bookings1_.id as id1_0_1_,
user0_.name as name2_1_0_,
bookings1_.country as country2_0_1_,
bookings1_.price as price3_0_1_,
bookings1_.user_id as user_id4_0_1_,
bookings1_.user_id as user_id4_0_0__,
bookings1_.id as id1_0_0__
from
user user0_
left outer join
booking bookings1_
on user0_.id=bookings1_.user_id
where
bookings1_.id is null
or bookings1_.price>?
It seems there is no JPA way for this. But I got it fixed in Hibernate way, using Filters org.hibernate.annotations.Filter.
#Entity
#FilterDef(name = "anyName", parameters = {
#ParamDef(name = "price", type = "integer")
})
public class User {
#Id
private Long id;
private String name;
#OneToMany(cascade = ALL, fetch = EAGER, orphanRemoval = true, mappedBy = "user")
#Filter(name = "anyName", condition = "price > :inputPrice")
private List<Booking> bookings;
}
Before querying the db, you must enable this filter.
Session session = enityManager.unwrap(Session.class);
session.enableFilter("anyName").setParameter("inputPrice", 2000);
// fetch using hql or criteria; but don't use booking.price.gt(2000) or similar condition there
session.disableFilter("anyName");
Now the result will have a User even if all of his booking prices are below 2000 and bookings list will be empty as expected.
NOTE: The word price in condition should be exactly same as the db column name; not as the model property name.
I have a MySQL table:
mysql> show create table items\G
*************************** 1. row ***************************
Table: items
Create Table: CREATE TABLE `items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
1 row in set (0.02 sec)
I create new rows from a Java program via an Entity class:
#Entity
#Table(name = "items", schema = "office_db")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Items.findAll", query = "SELECT i FROM Items i"),
#NamedQuery(name = "Items.findById", query = "SELECT i FROM Items i WHERE i.id = :id"),
#NamedQuery(name = "Items.findByName", query = "SELECT i FROM Items i WHERE i.name = :name"),
#NamedQuery(name = "Items.findByCreated", query = "SELECT i FROM Items i WHERE i.created = :created")
})
public class Items implements Serializable {
#Column(name = "name",length = 128)
private String name;
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#Column(name = "created")
#Temporal(TemporalType.TIMESTAMP)
private Date created;
#OneToMany(mappedBy = "itemId")
private Collection<Documents> documentsCollection;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "items")
private Collection<ItemAttributes> itemAttributesCollection;
(more stuff ...)
I only set the NAME column, and as expected, the ID and CREATED are set by default:
mysql> select * from items;
+----+--------+---------------------+
| id | name | created |
+----+--------+---------------------+
| 2 | Case 2 | 2017-10-31 13:47:52 |
| 3 | Case 3 | 2017-10-31 13:48:02 |
+----+--------+---------------------+
2 rows in set (0.00 sec)
However, when I reload the table into Java later in the same session:
public List<Items>findItems(){
TypedQuery<Items> query=
em.createNamedQuery("Items.findAll",Items.class);
return query.getResultList();
}
the ID column is loaded correctly, but the CREATED column comes up as blank. CREATED shows up correctly if I relaunch the application (this runs on a glassfish server). My guess is that the reason for this difference is the #GeneratedValue annotation on ID, but I can't apply it on the CREATED field, it seems, or at least not naively. What is the correct way to make the generated timestamp load?
The answer to my conundrum, it appears, is to call em.refresh(), according to this: Invalidating JPA EntityManager session - this is for Hibernate, but it seems to be the same for EclipseLink. I run in to an exception, in my case, but I will post that in another question.
ProcessSolution Entity :
#Entity
#Table(name="process_solution")
public class ProcessSolution implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="process_id", columnDefinition="INT(10) UNSIGNED")
private Integer processId;
#Column(name="process_name", length=120, nullable=false)
private String processName;
#ElementCollection(fetch=FetchType.LAZY)
//#LazyCollection(LazyCollectionOption.FALSE)
//#Fetch(FetchMode.Select)
#JsonIgnore
#CollectionTable(name="process_solution_step",
joinColumns=#JoinColumn(name="process_id"),
foreignKey=#ForeignKey(name="fk_process_solution_step_process_id")
)
#Column(name="solution_step", length=200, nullable=false)
private List<String> processSolutionSteps = new ArrayList<>();
#ManyToOne
#JoinColumn( name="category_id", columnDefinition="INT(10) UNSIGNED",nullable=false,
foreignKey=#ForeignKey(name="fk_process_solution_category")
)
private Category category;
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(name="process_solution_employee",
joinColumns={#JoinColumn(name="process_id")},
inverseJoinColumns={#JoinColumn(name="emp_id",columnDefinition="INT(10) UNSIGNED")},
foreignKey=#ForeignKey(name="fk_process_employee_process_solution_process_id"),
inverseForeignKey=#ForeignKey(name="fk_process_employee_employee_emp_id")
)
private Set<Employee> employees = new HashSet<>();
// Getters/Setters
}
And I'm Executing HQL Query in DAO as:
#Override
public ProcessSolution getProcessSolution(Integer processId) {
Session session = this.sessionFactory.openSession();
final String GET_PS = "SELECT ps FROM ProcessSolution ps JOIN FETCH ps.processSolutionSteps JOIN FETCH ps.employees WHERE ps.processId = :processId";
//ProcessSolution processSolution = session.get(ProcessSolution.class, processId);
ProcessSolution processSolution = ( ProcessSolution ) session.createQuery(GET_PS)
.setInteger("processId", processId).uniqueResult();
session.close();
return processSolution;
}
My Problem is I'm Getting ElementCollection i.e. processSolutionSteps repeated (Multiple Rows).
So I changed it From List<> to Set<>, now I'm getting correct result but its order is not preserved.
What I have tried:
For Set I have tried LinkedHashSet but problem still persist.
#LazyCollection(LazyCollectionOption.FALSE) from here
#Fetch(FetchMode.Select) from another SO source
Any Idea how to solve this problem.
Updated :
Sample Data :
**process_solution**
+---------------+----------------+
| process_id | process_name |
+---------------+----------------+
| 3 | process 1 |
+---------------+----------------+
**process_solution_step**
+---------------+----------------+
| process_id | solution_step |
+---------------+----------------+
| 3 | step 1 |
+---------------+----------------+
| 3 | step 2 |
+---------------+----------------+
If I print Process Solution Steps I get the result as
step 1
step 1
step 2
step 2
If I print Employee Lenth I got correct result.