How can I load database entries into my scala template? - database

I am using play 2.3.8 and building a program where you can ask questions and answer them. I have several different users, so I use their unique email as the ID in the database.
Questions / answers have an ownerID, to show who has written them.
My users are stored in the DB like this:
Email Name Password
bob#mail.com Bob secret
My questions are stored in the DB like this:
QUESTION_ID QUESTION_TEXT VOTE_SCORE OWNER_ID PAGE
77b7f88a-41df-4d68-9f89-de508fce8f71 How tall is tall? 1228 bob#mail.com 1
My controller class, that collects the questions / answers from the DB and sends the lists to the view class:
public static List<Question> questionListAll = new ArrayList<Question>();
public static List<Answer> answerListAll = new ArrayList<Answer>();
public static Result index() {
questionListAll.clear();
answerListAll.clear();
// Get all questions from DB
for (Question questionItem : Question.find.all()) {
questionListAll.add(questionItem);
}
// Get all answers from DB
for (Answer answerItem : Answer.find.all()) {
answerListAll.add(answerItem);
}
Collections.sort(questionListAll, Collections.reverseOrder());
Collections.sort(answerListAll, Collections.reverseOrder());
return ok(views.html.index.render(questionListAll, answerListAll));
}
User.java:
// FIXME Dont really save the password as String...
#Id
public String email;
public String name;
public String password;
(...)
Question.java:
#Entity
public class Question extends Model implements Comparable<Question> {
#Id
public String questionID;
public String questionText;
public Integer voteScore;
public String ownerID;
public Integer page;
(...)
}
In my view class I show the questions and behind them the user by using #answer.ownerID ... but as I use the email-field as ID, the entry is something like:
"How tall is tall?" - bob#mail.com
What I want is:
"How tall is tall?" - Bob Ross (using their name and not their email)
I know I could simply find all questions and their owners in my controller and put the users into another list, from where I would use #users.username, but this would mean I always have to look up all users, put them into the list, ...
So is it possible to look into the DB, use the ownerID and get the username in the view class?
Or more general: Can you look up DB entries for the view class from the view class itself?

You may add mappings with JPA 2.0 to achieve what you want in your view, then you may use #question.owner.name or #answer.owner.name.
User.java
[...]
#Id
#Column(name="user_id")
public Long userId;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "owner", cascade = CascadeType.ALL)
public List<Question> questionList;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "owner", cascade = CascadeType.ALL)
public List<Answer> answerList;
[...]
Question.java
[...]
#Id
#Column(name="question_id")
public Long questionId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="user_id", nullable = false)
public User owner;
// #OneToMany: ORM join relationship, one Question to many Answers
// FetchType.LAZY: ORM will not fetch from db until you use it
// CascadeType.ALL: any changes to question will be propagated
// onQuestionDelete: all answers associated with the question will be deleted
// onQuestionUpdateAnswerList: any updates made will reflect on db
// mappedBy: Question object variable name on Answer object for ORM to make a connection
#OneToMany(fetch = FetchType.LAZY, mappedBy = "question", cascade = CascadeType.ALL)
public List<Answer> answerList;
[...]
Answer.java
[...]
#Id
#Column(name="answer_id")
public Long answerId;
// FetchType.EAGER: ORM will fetch this from db when Answer is fetch
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="user_id", nullable = false)
public User owner;
// #ManyToOne: ORM join relationship, many Question to one Answer
// #JoinColumn: name, pk column name of Question; nullable false, required constraint;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="question_id", nullable = false)
public Question question;
[...]

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.

JPA entity relation in table which sharing same key as foreign and primary?

Now, I have two tables.
1st Table (Object Table):
RefNo (PK) --> auto ascending
Type
Status
...
2nd Table (Object Detail Table):
RefNo (PK) --> FK reference from 1st Table
PolicyNo
DepNo
...
Entity for Object.
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long refnno;
#OneToOne(mappedBy = "obejctTwo")
private ObjectDetail objectDetail;
private String type;
private String status;
Entity for ObjectDetail
#Id
private long refnno;
#OneToOne
#JoinColumn(name = "refnno")
#MapsId
private Object object;
private String policyNo;
private String depNo;
How can I save the Object using jpaRepository for Object which include ObjectDetail inside Object JSON but without knowing the reffno(PK) which is auto generated by db.
{
"objectDetail": {
"policyNo": "12345678",
"depNo": "ABC"
},
"type": "new",
"status": "pending"
}
Would it not be possible to cascade the saving with the annotation #OneToOne(cascade = CascadeType.PERSIST) in the class Object?
I would then create the two objects with the available details, set the relationship and save the class Object.
object.setObjectDetail(objectDetail);
objectdetail.setObject(object);
repository.save(object);
The key of object would be set automatically and objectDetail would get the key from object.
Edit: Entity Example
I was slightly surprised that the code did not work. Therefore, I implemented this for myself. It works perfectly with Spring Boot version 2.3.2.RELEASE and spring-boot-starter-data-jpa.
Your issue might be #OneToOne(mappedBy = "obejctTwo") because this mapping does not exist.
Object
#Entity
public class Object {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long refnno;
#OneToOne(mappedBy = "object",
cascade = CascadeType.ALL,
orphanRemoval = true)
private ObjectDetail objectDetail;
private String type;
private String status;
public setObjectDetail(ObjectDetail objectDetail) {
this.objectDetail = objectDetail;
objectDetail.setObject = this;
}
// Getters and remaining setters...
}
I added a slightly modified setter for objectDetail which helps to keep the bidirectional OneToOne mapping synchronised.
BTW: I changed the datatype of refnno to the object Long. Classes are considered to be better for database entities because than you can test them properly for null. Furthermore, this id can then be used for a JpaRepository<Object, Long>.
ObjectDetail
#Entity
public class ObjectDetail {
#Id
private Long refnno;
#OneToOne
#JoinColumn(name = "refnno")
#MapsId
private Object object;
private String policyNo;
private String depNo;
// Getters and setters...
}
Repository and Execution
I created a simple repository.
public interface ObjectRepository extends JpaRepository<Object, Long> {
}
I then used the save(Object entity) method to persist a new Object with a new ObjectDetail.
Object object = new Object();
object.setType("new");
ObjectDetail objectDetail = new ObjectDetail();
objectDetail.setPolicyNo = "999";
object.setObjectDetail(objectDetail);
objectRepository.save(object);

#ManyToOne mapping without an entity for the intermediate table

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.

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

Resources