#ManyToOne mapping without an entity for the intermediate table - database

I am writing an application that will query a massive Database, that cannot be changed.
For that reason, my application does not need to map all Objects, since that would be useless and time consuming.
All entities there mapped are #Immutable.
I came across this relationship:
I want to Map Order, and have it reference Customer. It is, in fact, a Many to One Relationship, it just happens two be two Join clauses away.
I am not interested in neither R nor B, since they convey no information related to my requirement.
I envision something like this, but I know the syntax is invalid:
#Entity
#Immutable
#Table(name = "Order")
public class Order implements Serializable {
#Id
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumns(value =
#JoinColumn(table = "R", name = "id", referencedColumnName = "R_id"),
#JoinColumn(table = "Customer", name = "id", referencedColumnName = "Customer_id")
)
private Customer customer;
... more data and getters/setters omitted ...
}
#Entity
#Immutable
#Table(name = "Customer")
public class Customer implements Serializable {
#Id
#Column(name = "id")
private Long id;
... more data and getters/setters omitted ...
}
Is there a way I can do this, without creating an entity for R?
EDIT: -------------------------
I tried the following, as per suggestion:
#ManyToOne
#JoinTable(name = "R",
joinColumns = #JoinColumn(name = "id", referencedColumnName = "R_id"),
inverseJoinColumns = #JoinColumn(name = "id", referencedColumnName = "Customer_id"))
private Customer customer;
However, I get the following error:
Unable to find column with logical name: R_id in org.hibernate.mapping.Table(Order) and its related supertables and secondary tables

You could use the #JoinTable annotation for the following schema.
in this way
#Entity
#Table(name = "Order")
public class Order {
// ...
#ManyToOne
#JoinTable(
name = "R",
joinColumns = #JoinColumn(name = "ord_id"),
inverseJoinColumns = #JoinColumn(name = "customer_id"))
private Customer customer;
// ...
}
But for your case it looks like not possible to avoid usage of entity for intermediate table R due to the lack of foreign key to the Order table in the R.

Related

How to update tables with many-to-many relationship when performing crud operations in Spring Boot

I'm trying to create a Spring Boot backend for my project. In the database I have Deck and Word tables with a many-to-many relationship connected via DeckWord table. The bridge table has additional fields and a composite PK consisting of the other 2 tables' PK's.
I am not sure about how I should structure the crud operations in my project. Say I'm trying to add a new word and it should be assigned to a certain deck. What model's controller should handle the post operation in that scenario: Word or DeckWord? Should the Deck's List<DeckWord> be updated as well?
UPDATE:
Included the models, omitted the getters, setters and constructors
#Entity
#Table(name = "deck")
public class Deck {
#Id
#SequenceGenerator(
name = "deck_sequence",
sequenceName = "deck_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "deck_sequence"
)
#Column(name = "deck_id")
private Long id;
#Transient
private Boolean learnt;
private String name;
#OneToMany(mappedBy = "deck", cascade = CascadeType.ALL)
private List<DeckWord> deckwords;
#ManyToOne
#JoinColumn(name="appuser_id",referencedColumnName="appuser_id")
private Appuser appuser;
}
and
#Entity
#Table(name = "word")
public class Word {
#Id
#SequenceGenerator(
name = "word_sequence",
sequenceName = "word_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "word_sequence"
)
#Column(name = "word_id")
private Long id;
private String definition;
private String transcription;
#OneToMany(mappedBy = "word", cascade = CascadeType.ALL)
private List<DeckWord> deckwords;
}
and the bridge table:
#Embeddable
class DeckWordKey implements Serializable {
#Column(name = "deck_id")
Long deckId;
#Column(name = "word_id")
Long wordId;
}
#Entity
#Table
public class DeckWord {
#EmbeddedId
DeckWordKey id;
#ManyToOne
#MapsId("deckId")
#JoinColumn(name = "deck_id",referencedColumnName="deck_id")
Deck deck;
#ManyToOne
#MapsId("wordId")
#JoinColumn(name = "word_id",referencedColumnName="word_id")
Word word;
private Boolean learnt;
private LocalDate last_checked;
private WordGroup wordGroup;
}
Answering your questions:
What model's controller should handle the post operation in that scenario: Word or DeckWord?
Given that a Word should always be assigned to a Deck, then I would use a POST request to the URL "/decks/{deckId}/words" to create a new Word. The request body should include definition and transcription.
Should the Deck's List be updated as well?
Yes, it must. For that, you need to use deckId that you receive as a path parameter.

Spring JPA inserts duplicate select columns when using #MapsId, #AttributeOverride on embedded composite key

I'm developing a Spring Boot REST API application and I've encountered a problem with SQL generation for Transact-SQL (SQL Server) dialect and I'm not sure where I did something wrong.
The application is about storage management and I have two entities: Part and Stock. I've simplified the structure to be as simple as possible.
I have composite PK - PartPK:
#Data #Embeddable
class PartPK {
#Column(name = "PART_ID")
private String partId;
#Column(name = "PART_ORGANIZATION_ID")
private String orgId;
}
... and the entity Part having PartPK as #EmbeddedId:
#Entity #Table(name = "parts")
class Part {
#EmbeddedId
private PartPK id;
}
then I'm having a Stock entity that ties to a Part entity and stores. The entity has a composite PK with the following structure, where I'm overriding attributes from PartPK (giving them STOCK_ prefix)
#Data #Embeddable
class StockPK {
#Column(name = "STOCK_STORE_ID")
private String storeId;
#Embedded
#AttributeOverrides({
#AttributeOverride(name = "partId", column = #Column(name = "STOCK_PART_ID")),
#AttributeOverride(name = "orgId", column = #Column(name = "STOCK_PART_ORGANIZATION_ID")),
})
private PartPK partId;
}
... and enclosing Stock entity where I'm trying to reference the Part entity using #MapsId:
#Entity #Table(name = "stocks")
class Stock {
#EmbeddedId
private StockPK id;
#MapsId("partId")
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "STOCK_PART_ID", referencedColumnName = "PART_ID"),
#JoinColumn(name = "STOCK_PART_ORGANIZATION_ID", referencedColumnName = "PART_ORGANIZATION_ID"),
})
private Part part;
}
Which compiles, but after executing a query from the repository, it generates the following query:
select TOP (?)
stockdb0_.stock_part_organization_id as bis_part0_0_,
stockdb0_.stock_store_id as bis_stor3_0_,
stockdb0_.stock_part_organization_id as bis_part5_0_,
stockdb0_.stock_part_id as bis_part6_0_
from stocks stockdb0_
As you can notice, for some reason it uses 2 times stock_part_organization_id column. The entity has incorrect values after persistence mapping (two Stock rows having the same Store but different parts are considered to be the same entity). When the part attribute is removed from the Stock entity, the query and resulting persistence mapping is correct.
Is there anything I'm doing wrong?
I'm using Spring Boot 2.4.5 (the latest) and Started Data Jpa of the same version.
I think using #IdClass will work better in this case:
class StockPK implements Serializable {
private String storeId;
private Part part;
...
}
#Entity #Table(name = "stocks")
#IdClass(StockPK.class)
class Stock {
#Id
private String id;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "STOCK_PART_ID", referencedColumnName = "PART_ID"),
#JoinColumn(name = "STOCK_PART_ORGANIZATION_ID", referencedColumnName = "PART_ORGANIZATION_ID"),
})
private Part part;
...
}
But if you want to use #EmbeddedId:
#Embeddable
public static class StockPK implements Serializable {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "STOCK_PART_ID", referencedColumnName = "PART_ID"),
#JoinColumn(name = "STOCK_PART_ORGANIZATION_ID", referencedColumnName = "PART_ORGANIZATION_ID"),
})
private Part part;
#Column(name = "STOCK_STORE_ID")
private String storeId;
}
#Entity #Table(name = "stocks")
class Stock {
#EmbeddedId
private StockPK id;
// The association is already defined in the key
}
Anyway, you don't have to use #MapsId (that's for something else) and you can find examples of both approaches with more details in the Hibernate ORM documentation.

Spring JPA - Multiply nested tables applying WHERE clause

I already created question before which was complicated. So I am creating a new one which will be as simple as possible.
I am having a problem with Spring JPA especially twice nested structure.
Here is a super simple relationship diagram:
The country has multiple rivers and rivers can flow through multiple countries. The river has multiple bridges but the bridge can be built over 1 river only. So M:N -> 1:N
This is the expected result:
{
countries: [
{
countryID: 1,
rivers: [
{
riverID: 1,
bridges: [{
bridgeID: 1
}]
}
]
}
]
}
And this is what I have.
Country entity
public class Country {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
... more columns
#ManyToMany(fetch = FetchType.EAGER,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "country_river",
joinColumns = { #JoinColumn(name = "countryid") },
inverseJoinColumns = { #JoinColumn(name = "riverid") })
private Set<River> rivers = new HashSet<>();
River entity
public class River {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
... more columns
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
#JoinColumn(name = "riverid")
private Set<Bridge> Bridges;
Don't need to mention Bridge entity since there is only ID column specified ( I am using unidirectional relation)
The problem is, that bridges are not filtered based on country. As you can see in the expected output, I would like to list all countries and it's associated rivers and bridges on them in the given country.
Do I have to create a custom query on Bridges inside River entity (If so, how.)? Or do I have to somehow change the structure of the database?

Join 3 table using Hibernate Entities with multiple columns

Can someone help me how should I join those three tables using JPA?
I already did 2 of 3 entities but please let me know if are ok:
#Entity
public class Pacienti {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String nume;
private String prenume;
//setters & getters
}
#Entity
public class Chestionare {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Id
#Column(name = "id_intrebare")
#GeneratedValue(strategy = GenerationType.AUTO)
private int idIntrebare;
private String intrebare;
//setters & getters
}
As I promise I come back after I'm generating entities automatically. Unfortunately now I have another problem.
Now I have the entity:
#Entity
#Table(name = "pacienti")
#NamedQuery(name = "Pacienti.findAll", query = "SELECT p FROM Pacienti p")
public class Pacienti implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(unique = true, nullable = false)
private int id;
#Column(nullable = false, length = 20)
private String nume;
#Column(nullable = false, length = 20)
private String prenume;
// bi-directional many-to-one association to Consultatii
#OneToMany(mappedBy = "pacienti")
private List<Consultatii> consultatiis;
// bi-directional many-to-one association to DetaliiPacient
#OneToMany(mappedBy = "pacienti")
private List<DetaliiPacient> detaliiPacients;
// bi-directional many-to-one association to Doctori
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id_doctor", nullable = false)
private Doctori doctori;
// bi-directional many-to-one association to RaspunsChestionar
#OneToMany(mappedBy = "pacienti")
private List<RaspunsChestionar> raspunsChestionars;
public Pacienti() {
}
//setters and getters
}
But when I do :
Query queryResult = sessionFactory.getCurrentSession().createQuery("from Pacienti");
I'm getting:
Pacienti is not mapped [from Pacienti] Error.
Can someone tell me why? I also tried "pacienti is not mapped [from pacienti]" but same result
Thank you!
I would recommend you to use the jpa tools/plugins available with the IDEs which will auto generate these jpa entities for you using the database tables rather than manually creating these.
And they will take care of setting the relationship b/w different entities(db tables) in the auto generation process itself.
If you are Eclipse you can achieve this.
The problem is bcz there is no query with the name "from pacienti" in place of that pass the query name "Pacienti.findAll" in your createQuery method.
Plz let ne know once you try this, if you face any problem

hibernate ignoring the cascade attribute

i'm using hibernate annotations in my project, I've created the tables and it's all good except that when i check the database their's no cascade even tho I've made sure to put it in the classes.
here is an exemple of how i do it :
public class Item implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 8271695210797279161L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "IDitem", unique = true, nullable = false)
private int iditem;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "IDDIVISION", nullable = false)
private Division division;
#Column(name = "SIGLE", length = 254)
private String sigleItem;
#Column(name = "DESCRIPTION", length = 254)
private String description;
....
}
i have cascade = CascadeType.ALL in all the ManyToOne, OneToMany and ManyToMany cases.
i tried also to add
#OnDelete(action = OnDeleteAction.CASCADE)
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
like mentioned here but nothing changed
can you help me solve the problem? thank you!
I had the same problem. Maybe you forget to add the child to the set of the parent.
division.getItems().add(item); // <-- That was my mistake.
item.setDivision(division);
I think the problem is that you don't use the correct database engine.
You should specify hibernate dialect as: org.hibernate.dialect.MySQLInnoDBDialect instead of MySQLDialect. Otherwise, it will be default to MyISAM, which doesn't support relation.

Resources