Dynamic #Table name based on input - java

We have Hibernate based application where due to a large data set, two sets of tables are created where user_id will either be mapped in the UserTickets table or RestOfWorldTickets table.
Would like to know how #Table on the entity java objects can be dynamically mapped based on some user selection.
#Entity
#Table(name = "**UserTickets**")
public class UserTickets {
#Id
#Column("Internal_id")
#GeneratedValue(strategy = IDENTITY)
private int internalId;
#Column("user_id")
private int userId;
#Column("state")
private String state;
#Column("city")
private String city;
#Column("address")
private String address;
#Column("ticketNumber")
private String ticketNumber;
..
// Setters and Getters
}
UserTickets DB Table
Internal_id | User_id | State | City | Address | ticket_number | ...
101 | 1025 | AZ | Tuscan | .. | 10256912 |
102 | 1026 | NC | Durham | .. | 10256983
RestOfWorldTickets DB Table
Internal_id | User_id | State | City | Address | ticket_number |..
101 | 1058 | {null} | London | .. | 102578963 |..
102 | 1059 | {null} | Berlin | .. | 112763458 |..
The user and table mapping are now defined in a new table.
TableMapping Database table.
Internal_id | User_id | TableMapped |
1 | 1025 | UserTickets |
2 | 1026 | UserTickets |
3 | 1058 | RestOfWorldTickets |
4 | 1059 | RestOfWorldTickets |
So, using the UserTickets result set, how I map #Table attribute on the UserTickets Java object dynamically so that my Criteria API queries will work automatically without changing them to HQL queries?
Maybe using Interceptors http://docs.jboss.org/hibernate/orm/3.5/javadocs/org/hibernate/Interceptor.html?

I am quite unsure what you actually need but i try to give my solution based on a few quesses. Changing #Table dynamically is not -afaik- possible but if i guessed right you could have some benefit of inheritance in this case:
1st modify UserTickets to allow inheritance
#Entity
//#Table(name = "**UserTickets**")
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class UserTickets {
#Id // this annotation was missing from yours ?
#Column(name="Internal_id")
#GeneratedValue(strategy = GenerationType.SEQUENCE)
// identity generated problems in openjpa so i changed it to SEQUENCE
private int internalId;
#Column(name="user_id") private int userId;
#Column(name="state") private String state;
#Column(name="city") private String city;
#Column(name="address") private String address;
#Column(name="ticketNumber") private String ticketNumber;
}
2nd create a new entity
#Entity
public class RestOfWorldTickets extends UserTickets {
// yes i am just an empty class, TABLE_PER_CLASS gives me the fields
}
This allows you to use criteriaqueries against UserTickets but in addition the queries are done against RestOfWorldTickets also. So now when you search with user id result set will contain results from both tables. Checking/ loggin -for example with instanceof operator- you can see which one the ticket is.
"Disclaimer" i am using and testing with openjpa so there can be some differences/probkems with this solution...

Related

QueryDSL intersection with Spring Boot Data JPA

I am using QueryDSL within a Spring Boot, Spring Data JPA project.
I have the following schema for a table called test:
| id | key | value |
|----|------|-------|
| 1 | test | hello |
| 1 | test | world |
| 2 | test | hello |
| 2 | foo | bar |
| 3 | test | hello |
| 3 | test | world |
Now I want to write the following SQL in QueryDSL:
select id from test where key = 'test' and value = 'hello'
INTERSECT
select id from test where key = 'test' and value = 'world'
Which would give me all ids where key is 'test' and values are 'hello' and 'world'.
I did not find any way of declaring this kind of SQL in QueryDSL yet. I am able to write the two select statements but then I am stuck at combining them with an INTERSECT.
JPAQueryFactory queryFactory = new JPAQueryFactory(em); // em is an EntityManager
QTestEntity qTestEntity = QTestEntity.testEntity;
var q1 = queryFactory.query().from(qTestEntity).select(qTestEntity.id).where(qTestEntity.key("test").and(qTestEntity.value.eq("hello")));
var q2 = queryFactory.query().from(qTestEntity).select(qTestEntity.id).where(qTestEntity.key("test").and(qTestEntity.value.eq("world")));;
In the end I want to retrieve a list of ids which match the given query. In general the amount of intersects can be something around 20 or 30, depending on the number of key/value-pairs I want to search for.
Does anyone know a way how to do something like this with QueryDSL ?
EDIT:
Assume the following schema now, with two tables: test and 'user':
test:
| userId | key | value |
|---------|------|-------|
| 1 | test | hello |
| 1 | test | world |
| 2 | test | hello |
| 2 | foo | bar |
| 3 | test | hello |
| 3 | test | world |
user:
| id | name |
|----|----------|
| 1 | John |
| 2 | Anna |
| 3 | Felicita |
The correspond java classes look like this. TestEntity has a composite key consisting of all of its properties.
#Entity
public class TestEntity {
#Id
#Column(name = "userId", nullable = false)
private String pubmedId;
#Id
#Column(name = "value", nullable = false)
private String value;
#Id
#Column(name = "key", nullable = false)
private String key;
}
#Entity
class User {
#Id
private int id;
private String name;
#ElementCollection
private Set<TestEntity> keyValues;
}
How can I map the test table to the keyValues properties within the User class?
Your TestEntity is not really an Entity, since it's id is not a primary key, it's the foreign key to the user table.
If it's only identifiable by using all its properties, it's an #Embeddable, and doesn't have any #Id properties.
You can map a collection of Embeddables as an #ElementCollection part of another entity which has the id as primary key. The id column in your case is not a property of the Embeddable, it's just the foreign key to the main table, so you map it as a #JoinColumn:
#Embeddable
public class TestEmbeddable {
#Column(name = "value", nullable = false)
private String value;
#Column(name = "key", nullable = false)
private String key;
}
#Entity
class User {
#Id
private int id;
#ElementCollection
#CollectionTable(
name="test",
joinColumns=#JoinColumn(name="id")
)
private Set<TestEmbeddable> keyValues;
}
In this case, the QueryDSL becomes something like this (don't know the exact api):
user.keyValues.any().in(new TestEmbeddable("test", "hello"))
.and(user.keyValues.keyValues.any().in(new TestEmbeddable("test", "world"))
In this case I'd probably just use an OR expression:
queryFactory
.query()
.from(qTestEntity) .select(qTestEntity.id)
.where(qTestEntity.key("test").and(
qTestEntity.value.eq("hello")
.or(qTestEntity.value.eq("world")));
However, you specifically mention wanting to use a set operation. I by the way think you want to perform an UNION operation instead of an INSERSECT operation, because the latter one would be empty with the example given.
JPA doesn't support set operations such as defined in ANSI SQL. However, Blaze-Persistence is an extension that integrates with most JPA implementations and does extend JPQL with set operations. I have recently written a QueryDSL extension for Blaze-Persistence. Using that extension, you can do:
List<Document> documents = new BlazeJPAQuery<Document>(entityManager, cbf)
.union(
select(document).from(document).where(document.id.eq(41L)),
select(document).from(document).where(document.id.eq(42L))
).fetch();
For more information about the integration and how to set it up, the documentation is available at https://persistence.blazebit.com/documentation/1.5/core/manual/en_US/index.html#querydsl-integration

Hibernate, how to do a many-to-one mapping on a may-be-unique column, ignoring duplication

I have 2 Hibernate entities:
#Entity
class Car {
#ManyToOne
#JoinColumn(name = "brand_code")
private BrandReference brand;
}
#Entity
class BrandReference {
#Column(name = "brand_code")
private String brandCode;
}
The table BrandReference will have data like this:
row_id | brand_code | brand_name
--------------------------------
1 | TOYO | Toyota
2 | BENZ | Mercedes-Benz
3 | HOND | Honda
The column brand_code actually is expected to be unique. But I am in a situation where this expectation cannot be controlled. The data in table BrandReference is controlled by external party and it is unavoidable to have an error that cause the table to have more than 1 rows with the same brand_code
row_id | brand_code | brand_name
--------------------------------
1 | TOYO | Toyota
2 | BENZ | Mercedes-Benz
3 | HOND | Honda
4 | HOND | Gonda (Typo)
I don't like the program to crash on this situation. Is there a way to tell Hibernate to choose the row with the least row_id in case it found more than 1 rows with the matched brand_code when assigning a BrandReference to a Car. Something like this.
#Entity
class Car {
#ManyToOne
#JoinColumn(name = "brand_code")
#GetFirstRow(orderBy = "row_id")
private BrandReference brand;
}

How to insert lists to a table without repeating them if they already exist (Using Spring and Hibernate)

first of all, I'd like to point out I'm new to Spring and Hibernate.
I've been trying to connect Hibernate with MySQL (using Spring Data), which I've done successfuly, however, when I try to do an insert I can't get it to do what I want.
What I'm trying to accomplish is to have a User class, with an ArrayList that contains their preferred coding languages.
For example:
User 1: C, Java, C++
User 2: Python, Java, C++
However, I've noticed that Hibernate will create a new row in the "Languages" table even if it's already there. I also want to keep the "id" as a primary key and as a numeric value.
The following is how it currently inserts the languages to the table:
+----+--------+
| id | name |
+----+--------+
| 2 | C |
| 3 | Java |
| 4 | C++ |
| 6 | Python |
| 7 | Java |
| 8 | C++ |
+----+--------+
This is how I want them:
+----+--------+
| id | name |
+----+--------+
| 2 | C |
| 3 | Java |
| 4 | C++ |
| 6 | Python |
+----+--------+
I've tried different methods, but it's always repeating the values...
This is my user class:
#Entity
public class User {
#Id
#GeneratedValue
private int id;
private String username;
#OneToMany(cascade = CascadeType.ALL)
private List<Language> languages;
...
}
And this is my Language class:
#Entity
public class Language {
#Id
#GeneratedValue
private int id;
private String name;
...
}
This is how I'm adding the languages to each user (it's just a test, not final code):
ArrayList<Language> languages = new ArrayList<>();
languages.add(new Language("C"));
languages.add(new Language("Java"));
languages.add(new Language("C++"));
Image image = null;
User admin = new User("iscle", "albertiscle9#gmail.com", "Test_Password", languages, image);
userRepository.save(admin);
ArrayList<Language> languages2 = new ArrayList<>();
languages2.add(new Language("Python"));
languages2.add(new Language("Java"));
languages2.add(new Language("C++"));
Image image2 = null;
User admin2 = new User("iscle", "albertiscle9#gmail.com", "Test_Password", languages2, image2);
userRepository.save(admin2);
make attribute name (Languages) "UNIQUE"; then you ensure that there will be no duplicate

Query Predicate in QueryDSL

The environment is Java, Spring-boot, Hibernat, QueryDSL, MySQL.
I have table structure
Episode
+----+-------------+--------
| id | address_id | eventno
+----+-------------+--------
| 5 | 27 | F123
| 6 | 30 | F456
| 7 | 45 | F789
+----+-------------+--------
#Entity
public class Episode {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotEmpty
private String eventno;
#ManyToOne(cascade = CascadeType.ALL)
private Address address;
Episode_Person
+----+--------------+--------------+------------+-----------+
| id | episode_role | primary_flag | episode_id | person_id |
+----+--------------+--------------+------------+-----------+
| 19 | Buyer | | 5 | 1 |
| 20 | Subject | | 5 | 2 |
| 23 | Witness | | 6 | 3 |
| 24 | Child | | 6 | 4 |
| 27 | Buyer | | 5 | 3 |
| 63 | Investor | | 5 | 4 |
| 64 | Subject | | 7 | 1 |
| 65 | Subject | | 7 | 3 |
#Entity
public class EpisodePerson {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#Valid
private Person person;
#ManyToOne
private Episode episode;
Person
+----+-----------+----------+
| id | firstname | surname |
+----+-----------+----------+
| 1 | Clint | eastwood |
| 2 | Angelina | joilee |
| 3 | Brad | pitt |
| 4 | Jennifer | aniston |
#Entity
#Table(uniqueConstraints = #UniqueConstraint(columnNames = {"nia"}))
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String surname;
private String firstname;
private String gender;
So each episode has multiple people. And the join table is Episode_Person.
My UI has a datatable with a filter on each column:
The filtering already works on Event and Address. And looks like this predicate in QueryDSL:
BooleanBuilder where = new BooleanBuilder();
if (pagination.getFilterBy().getMapOfFilters().get("eventno")!=null) {
where.and(qEpisode.eventno.containsIgnoreCase(pagination.getFilterBy().getMapOfFilters().get("eventno")));
}
if (pagination.getFilterBy().getMapOfFilters().get("address")!=null) {
where.and(qEpisode.address.formattedAddress.containsIgnoreCase(pagination.getFilterBy().getMapOfFilters().get("address")));
}
where.and(qEpisode.creatingUser.eq(user));
List<Episode> e = episodeRepository.findAll(where);
How would I now add a 3rd predicate for case name where case name is constructed of the first two people returned in the collection of people against a episode?
UPDATE
For clarification the DTO thats backs the UI view contains the "casename" attribute. It is created in the service layer when Domain objects are converted to DTO:
episodeDashboard.setNames(episodePersonList.get(0).getPerson().getSurname().toUpperCase() +" & " +episodePersonList.get(1).getPerson().getSurname().toUpperCase());
Not easily unless you delegate some of the processing to the database.
If we can get the case_name property to be populated at the database tier rather than as a derived property in the application logic then the front-end code becomes trivial.
We can do this by means of a view. The exact definition of this will depend on your database however the output would be something like this:
episode_summary_vw
+------------+-------------------------+
| epsiode_id | case_name |
+------------+-------------------------+
| 5 | Eastwood & Joilee|
| 6 | Pitt & Aniston|
| 7 | Aniston & Pitt|
+------------+-------------------------+
For Oracle it looks like LISTAGG function is what you would want and for MySQL the GROUP_CONCAT functions. In MySQL then I think this would look something like:
CREATE VIEW episode_summary_vw as
SELECT ep.episode_id, GROUP_CONCAT(p.surname SEPARATOR ' & ')
FROM episode_person ep
INNER JOIN person p on p.id = ep.person_id
GROUP BY ep.episode_id;
-- todo: needs limit to first 2 records
Once we have a view then we can simply map the case_name to the Episode entity using the #SecondaryTable functionality of JPA:
#Entity
#Table(name = "episodes")
#SecondaryTable(name = "episode_summary_vw", primaryKeyJoinColumna = #PrimaryKeyJoinColumn(name="episode_id", reference_column_name="id"))
public class Episode {
#Column(name ="case_name", table = "episode_summary_vw")
private String caseName;
}
You then filter and sort on the property as for any other field:
if (pagination.getFilterBy().getMapOfFilters().get("caseName")!=null) {
where.and(qEpisode.caseName.containsIgnoreCase(pagination.getFilterBy().
getMapOfFilters().get("caseName")));
}

Hibernate annotation many-to-one issue

I have some issue with save object with one to many relationship. In my problem One UserGrop has many UserPermissions. Forthat relation ship I have create my domain class like this:
#Entity
#Table(name = "tbl_usergroup")
public class UserGroup implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="userGroupId")
private long userGroupId;
#Column(name = "userGroupName")
private String userGroupName;
#OneToMany(cascade=CascadeType.ALL,mappedBy="userGroup",fetch=FetchType.EAGER)
private Set<UserPermissions> userPermissions = new HashSet<UserPermissions>(0);
}
#Entity
#Table(name = "tbl_group_permissions")
public class UserPermissions implements Serializable {
#Id
#GeneratedValue
private Long userPermissionId;
#ManyToOne(cascade =CascadeType.ALL)
#JoinColumn(name="userGroupId",nullable=false)
#ForeignKey(name = "userGroupId")
private UserGroup userGroup;
}
But When saving UserGroup, it save hat UserPermisions Objects also. There with the table it does not have any relationship(when retrieving UserGroup object, it doesn't return Set of UserPermisions objects).
DB:
+------------------+-------------+
--+
| userPermissionId | userGroupId |
e |
+------------------+-------------+
--+
| 33 | NULL |
|
| 34 | NULL |
|
| 35 | NULL |
|
| 36 | NULL |
|
| 37 | NULL |
|
| 38 | NULL |
|
| 39 | NULL |
|
| 40 | NULL |
|
+------------------+-------------+
--+
8 rows in set (0.00 sec)
Can any body help me to solve this issue?
In the #OneToMany on UserGroup.userPermissions you have mappedBy="userGroup". This means the userGroup property in UserPermisions is responsible for the relation. I guess you don't set that property and upon saving it's still null.

Categories