grails, relations and cascade delete - database

I have the following domain classes:
class Patient {
...
}
class Receipt{
#NotNull
static belongsTo = [patient:Patient]
...
}
If I try to delete a Patient instance (after creation of Receipt instances), I have a MySQLIntegrityConstraintViolationException. Notice that a patient can have zero-to-many receipts.

To complete the parent child relationship, create a has many section in the parent domain class:
class Patient {
static hasMany = [receipts: Receipt]
...
}
class Receipt{
#NotNull
static belongsTo = [patient:Patient]
...
}

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.

#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.

How can I load database entries into my scala template?

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;
[...]

Hibernate #OneToMany remove child from list when updating parent

I have the following entities:
TEAM
#Entity
#Table
public class Team {
[..]
private Set<UserTeamRole> userTeamRoles;
/**
* #return the userTeamRoles
*/
#OneToMany(cascade = { CascadeType.ALL }, mappedBy = "team", fetch = FetchType.LAZY)
public Set<UserTeamRole> getUserTeamRoles() {
return userTeamRoles;
}
/**
* #param userTeamRoles
* the userTeamRoles to set
*/
public void setUserTeamRoles(Set<UserTeamRole> userTeamRoles) {
this.userTeamRoles = userTeamRoles;
}
}
and
USER_TEAM_ROLE
#Entity
#Table(name = "user_team_role")
public class UserTeamRole {
#ManyToOne(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
#JoinColumn(name = "FK_TeamId")
public Team getTeam() {
return team;
}
}
Now, when updating a Team entity that contains for example Team.userTeamRoles = {UTR1, UTR2} with {UTR1, UTR3}, I want UTR2 to be deleted. But the way I do it now, the old list remains the same and it only adds UTR3 to the list.
This is how I do it at the moment:
if (!usersDualListData.getTarget().isEmpty()) {
// the role for each user within the team will be "employee"
team.setUserTeamRoles(new HashSet<UserTeamRole>());
Role roleForUser = roleService
.getRoleByName(RoleNames.ROLE_EMPLOYEE.name());
for (User user : usersDualListData.getTarget()) {
UserTeamRole utr = new UserTeamRole();
utr.setUser(user);
utr.setTeam(team);
utr.setRole(roleForUser);
team.getUserTeamRoles().add(utr);
}
}
teamService.updateTeam(team);
I thought that by doing team.setUserTeamRoles(new HashSet<UserTeamRole>()); the list would be reset and because of the cascades the previous list would be deleted.
Any help is appreciated. Thank you
Instead of replacing the collection (team.setUserTeamRoles(new HashSet<UserTeamRole>());) you have to clear() the existing one. This happens because if Hibernate loads the entity (and its collections) from DB, it "manages" them, ie. tracks their changes. Generally when using Hibernate it's better not to create any setters for collections (lists, sets). Create only the getter, and clear the collection returned by it, ie:
team.getUserTeamRoles().clear();
Another thing is that you miss orphan deletion (ie. delete child object when it's removed from collection in the parent). To enable it, you need to add #OneToMany(orphanRemoval=true) in owning entity.

How to change and entity type in Doctrine2 CTI Inheritance

How (if possible at all) do you change the entity type with Doctrine2, using it's Class Table Inheritance?
Let's say I have a Person parent class type and two inherited types Employe and Client. My system allows to create a Person and specify it's type - that's fairly easy to implement - but I'd also like to be able to change the person from an Employe to a Client, while maintaining the Person-level information (it's id and other associated records).
Is there a simple way to do this with Doctrine2?
I was looking for this behaviour yesterday also.
In the end, after speaking with people in #doctrine on freenode, I was told that it is not possible.
If you want to do this, then you have to go through this:
Upgrading a User
Grab the Person Entity.
Update the discrimator column so that it is no longer a 'person' and change it to 'employee'
Create a corresponding row inyour Employee table for this inheritance.
Removing Inheritance
Likewise if you want to remove inheritance, you have to..
Grab the Person Entity.
Update the discrimnator column so that it is no longer an 'employee' and change it to a 'person'.
Delete the corresponding row in your Employee table. (Yes you have to delete it, just change the discrimator coumn is not sufficient).
This might be 7 months late, but it is at least the correct answer for anything else looking to suport such a feature.
PHP doesn't have support for object casting, so Doctrine doesn't support it. To workaround the problem I write this static method into parent classes:
public static function castToMe($obj) {
$class = get_called_class();
$newObj = New $class();
foreach (get_class_vars(get_class($newObj)) as $property => $value) {
if (method_exists($obj, 'get' . ucfirst($property)) && method_exists($newObj, 'set' . ucfirst($property))) {
$newObj->{'set' . ucfirst($property)}($obj->{'get' . ucfirst($property)}());
}
}
return $newObj;
}
You can create this method in class Person and use it to cast from Employe to Client and viceversa:
$employe = New Employe();
$client = Client::castToMe($employe);
Now, if you want, you can remove the $employe entity.
You could do something like this though:
This Trait can be used on your Repository class:
namespace App\Doctrine\Repository;
trait DiscriminatorTrait
{
abstract public function getClassMetadata();
abstract public function getEntityManager();
private function updateDiscriminatorColumn($id, $class)
{
$classMetadata = $this->getClassMetadata();
if (!in_array($class, $classMetadata->discriminatorMap)) {
throw new \Exception("invalid discriminator class: " . $class);
}
$identifier = $classMetadata->fieldMappings[$classMetadata->identifier[0]]["columnName"];
$column = $classMetadata->discriminatorColumn["fieldName"];
$value = array_search($class, $classMetadata->discriminatorMap);
$connection = $this->getEntityManager()->getConnection();
$connection->update(
$classMetadata->table["name"],
[$column => $value],
[$identifier => $id]
);
}
}
There still might be some extra work you need to put in, like clearing values in fields that are only present on one of your sub-classes
In Doctrine2, when you have your parent entity class, Person set as:
/**
* #Entity
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="discr", type="string")
* #DiscriminatorMap({"person" = "Person", "employee" = "Employee", , "client" = "Client"})
*/
class Person
{
// ...
}
and sub classes such as Client set as:
/** #Entity */
class Client extends Person
{
// ...
}
when you instantiate Person as:
$person = new Person();
Doctrine2 checks your #DiscriminatorMap statement (above) for a corresponding mapping to Person and when found, creates a string value in the table column set in #DiscriminatorColumn above.
So when you decide to have an instance of Client as:
$client = new Client();
Following these principles, Doctrine2 will create an instance for you as long as you have declared the parameters in the #DiscriminatorMap. Also an entry will be made on the Person table, in the discr column to reflect that type of entity class that has just been instantiated.
Hope that helps. It's all in the documentation though
i use this method
trait DiscriminatorTrait
{
// ...
public function updateDiscriminatorColumn($id, $class)
{
// ... other code here
$connection->update(
"Person", // <-- just there i put my parent class
[$column => $value],
[$identifier => $id]
);
}
}
and i use call like this after :
$this->em->getRepository(Client::class)->updateDiscriminatorColumn($cCenter->getId(), Employe::class);
$this->em->close();
// I update the data directly without going through doctrine otherwise it will create a new Person
try {
$query = "
INSERT INTO Employe (id, /* ... other fields */)
VALUES ({$callCenter->getId()}, /* ... other fields */)
";
$results = $this->connection->executeQuery($query)->execute();
} catch (\Exception $exception) {
echo $exception->getMessage().PHP_EOL;
}
$this->em->close();
// i restart the connection
/** #var EntityManagerInterface $entityManager */
$entityManager = $this->em;
if ($this->em->isOpen() === false) {
$this->em = $entityManager->create(
$this->em->getConnection(),
$this->em->getConfiguration(),
$this->em->getEventManager()
);
}
// and there a get Employer en update him
$employe = $this->em->getRepository(Employe::class)->find($id);
$employe->setFirstname($callCenter->getFirstName());
// other code
And it is work for me

Resources