Spring boot Foreign key constraint fails - spring-boot

so I have a class Observer which needs to have a list of notifications I am using a OneToMany relation between the two classes, but for some reason when I want to insert a notification I get a foreign key constraint failure. I would greatly appreciate it if someone could help me or point me in the right direction, Thanks in advance
Observer class
#Entity
abstract class Observer {
#Id
#GeneratedValue
open var id: Long = -1;
#OneToMany
open var notifications: MutableList<Notification> = mutableListOf()
abstract fun update(podcast: Podcast);
}
// different file
#Entity
class User(
var name: String,
var password: String,
#OneToMany
var podcasts : MutableList<PodcastInfo> = mutableListOf(),
) : Observer() {
override fun update(podcast: Podcast) {
val notification = Notification(message = "There is a new episode of ${podcast.name}");
this.notifications.add(notification)
print("new episode for podcast ${podcast.name}")
}
}
Notification class
#Entity
class Notification(
#Id
#GeneratedValue
val id : Long = -1,
val date : LocalDateTime = LocalDateTime.now(),
val seen : Boolean = false,
val message: String
) {
}
The project is about podcasts and when a new episode is added I want to notify the observers
fun addEpisode(#ModelAttribute episodeRequest : EpisodeRequest, #PathVariable("id") id: Long) : String {
....
podcast.addEpisode(episode); // this line adds the notification to the observer
podcast.updateObservers(observerService)
...
}
// different file
fun updateObservers(observerService: ObserverService){
this.observers.forEach{observer -> observerService.updateObserver(observer) }
}
// different file, this is the only descendant of the observer class
fun updateObserver(observer: Observer) : Observer{
val notifications = mutableListOf<Notification>()
for (notification: Notification in observer.notifications){
notifications.add(notificationService.update(notification))
}
observer.notifications = notifications;
return observerService.save(observer)
}
// different file
fun update(notification: Notification) : Notification {
return notificationRepository.save(notification) // this line throws the error
}
So I thought since I am first inserting/updating the notifications and then updating the observer class, I wouldn't get the foreign key error since the notifications would have valid id's.
java.sql.SQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (podcasts.observer_notifications, CONSTRAINT FKgw8m1isyux27iyu8m0l57kdd6 FOREIGN KEY (notifications_id) REFERENCES notification (id))

You can either use #OneToMany(cascade = CascadeType.MERGE), so that notifications are flushed properly before the observer, or you invoke notificationRepository.flush() right after saving the notifications e.g. use:
fun updateObserver(observer: Observer) : Observer{
val notifications = mutableListOf<Notification>()
for (notification: Notification in observer.notifications){
notifications.add(notificationService.update(notification))
}
entityManager.flush(); // <--- This here
observer.notifications = notifications;
return observerService.save(observer)
}

Related

save() method of JPARepository to udate an existing entity in the db. But as soon as save() is called it is deleting the record

I am using save() method of JPARepository to update an existing entity in the db. But as soon as save() is called it is deleting the record which is suppose to be get update.
DummyBo is pojo for DummyEntity
Please refer below the Table description, using springboot annotations, for DummyEntity
#Table(
name = DummyEntity.MYTABLE_TABLE,
uniqueConstraints = #UniqueConstraint(
name = DummyEntity.UNIQUE_CONSTRAINT,
columnNames = {
DummyEntity.NAME_COLUMN,
DummyEntity.VERSION_COLUMN,
DummyEntity.S_GROUP_ID_COLUMN
}
)
)
public PNBo updateEntity(DummyBo dummyBo,
int version, String name, boolean force) {
DummyEntity dummyEntity = this.repository.findByNameAndValidityEndDateIsNull(name);
/*
* DummyEntity dummyEntity =
* this.repository.findByNameAndVersionAndServiceGroupId(name, version,
* dummyBo.getServiceGroup());
*/
if (dummyEntity == null) {
dummyEntity = new DummyEntity();
} else {
List<DummyAndTermEntity> ruleAndTerms = dummyEntity.getRuleAndTerms();
for (DummyAndTermEntity andTermEntity : ruleAndTerms) {
andTermEntity.getOrTerms().clear();
}
dummyEntity.getRuleAndTerms().clear();
}
}
//setting the fields which need to be update
dummyEntity.setName(name);
dummyEntity.setPartNumbers(dummyBo.getPNumber());
dummyEntity.setServiceGroupId(dummyBo.getSGroup());
dummyEntity.setValidityStartDate(dummyBo.getStartDate());
dummyEntity.setValidityEndDate(dummyBo.getEndDate());
dummyEntity.setVersion(version);
//here save method is being called to update the
this.repository.save(dummyEntity);
log.info("Updated successfully!!!Voilaa");
return this.mapper.DummyEntityToPNBo(dummyEntity);
}

Holding classes in Dictionary

I have a question about holding a class in dictionary.So I am working on a project about a university.There are more than one faculty names.When user types a faculty name,I am directing user to appropriate faculty class with using context.call
So in here,if user enters show me computer engineering,user directed to the ShowComp class.
But using if-else makes code really unreadable.I thought that I can put these keywords to dictionary
But this time context.Call gives an error about the value type.What should I put dictionary value type.I couldn't figure it out.Can anyone help me please?
Since Dialogs inherit from IDialog<object>, you can put that in the dictionary:
private readonly Dictionary<string, IDialog<object>> options
= new Dictionary<string, IDialog<object>>
{ { "computer", new ShowComp() }, { "law", new ShowLaw() } };
public async Task GetFacilities(IDialogContext context, LuisResult result)
{
var entity = result.Entities.FirstOrDefault(e => options.ContainsKey(e.Entity));
if (entity != null)
{
IDialog<object> dialog = null;
if (options.TryGetValue(entity.Entity, out dialog))
{
context.Call(dialog, this.AfterResume);
}
}
}
Do it like this:
static Dictionary<String, Type> FACULTY_CLASS_MAP;
/**
* faculty class mapping.
*/
FACULTY_CLASS_MAP= new Dictionary<String, Type>
{
{ "Text", typeof(FacultyClass) }
}

Multi database in the same method

My app has 2 databases, Db1 (has table dbo.Student) and Db2 (has table dbo.School). I created 2 AppService to access them
When I tried to get data from both of them, It only uses the connection to Db1 (Could not found table dbo.School in the current context). So how can I get the data from Db1 and Db2 at the same time
private readonly IStudentAppService _studentAppService;
private readonly ISchoolAppService _schoolAppService;
public BranchAccountController(IStudentAppService studentAppService,
ISchoolAppService schoolAppService)
{
_studentAppService = studentAppService;
_schoolAppService = schoolAppService;
}
public async Task<PartialViewResult> GetStudent(int? id)
{
//Repository 1 (Database 1)
var student = await _studentAppService.GetStudentForEdit(new NullableIdDto { Id = id });
//Repository 2 (Database 2)
var school = await _schoolAppService.GetSchoolList();
//bla bla
}
Update 1:
I tried to get the school before student and face the below error:
The transaction passed in is not associated with the current
connection. Only transactions associated with the current connection
may be used.
You have to Begin separate units of work:
public async Task<PartialViewResult> GetStudent(int? id)
{
List<SchoolDto> schools;
StudentDto student;
using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress))
{
// Repository 1 (Database 1)
student = await _studentAppService.GetStudentForEdit(new NullableIdDto { Id = id });
uow.Complete();
}
using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress))
{
// Repository 2 (Database 2)
schools = await _schoolAppService.GetSchoolList();
uow.Complete();
}
// ...
}

Life cycle callback prePersist not firing (using YAML files)

I'm learning Symfony following The Book. In the tutorial, I successfully configured prePersist events (to set the createdAt field at insert time).
Now I'm trying to do the same but with YAML files instead of annotations. Here my orm.yml file:
AppBundle\Entity\Chronicle:
type: entity
table: chronicles
id:
id:
type: integer
generator: {strategy: AUTO}
fields:
name:
type: string
length: 256
createdAt:
type: datetime
manyToOne:
creator:
targetEntity: User
inversedBy: chronicles
joinColumn:
name: user_id
referencedColumnName: id
game:
targetEntity: Game
joinColumn:
name: game_id
referencedColumnName: id
oneToMany:
characters:
targetEntity: Character
mappedBy: chronicle
lifeCycleCallbacks:
prePersist: [ setCreatedAtValue ]
And this is a snippet of my entity class:
class Chronicle
{
private $id;
private $name;
private $createdAt;
// Associations
private $game;
private $creator;
private $characters;
// TODO: users or user relationships
// Constructor
public function __construct(User $creator=null) {
$this->characters = new ArrayCollection();
$this->creator = $creator;
}
/**
* Set createdAt at the current time.
*
* (Lifecycle callback)
*/
public function setCreatedAtValue()
{
$this->createdAt = new \DateTime();
}
// ...
}
But the setCreatedAtValue() method is never called. So, I get an exception when I try to insert an object.
I have noticed that when using annotations I have to tell about the existence of lifecycle callbacks with the #ORM\HasLifecycleCallbacks() annotation, but I have not found anywhere an equivalent to that in yml, or if that is needed.
In a tutorial I have found I should register the callback in services.yml, but the tutorial never mention it, and I haven't found it anywhere else.
It will be called if you change "lifeCycleCallbacks" to "lifecycleCallbacks".

Get User custom fields without SOQL (like $User in formulas)?

I have some custom fields on my User object that I want to access with APEX code in my VisualForce trigger. When I access it from a Formula field I get to use a nifty $User reference like this:
$User.my_prop__c
From APEX I have to query the User object by UserId like this:
[select my_prop__c from User where id = :UserInfo.getUserId()].my_prop__c;
Is there something baked into APEX already that would let me get at the user properties without the SOQL query? If not, does anyone know of a utility class for lazy loading and caching user properties so the overhead is minimal.
I would use something similar to the following code sample. It uses a singleton pattern to statically store the information in memory for the duration of your transaction. It's similar to the lazy loading that twamley proposed but I feel this is a much simpler approach.
Usage 1: UserUtil.CurrentUser.Email;
Usage 2: User someUser = UserUtil.getUser(someUserId);
This will allow you to access the same information on the current user or other users in the system. Notice the queryUsers method just returns a query result. This makes it easy to add and remove fields from your query as it is isolated in its own method keeping things simple.
Note: that this code pulls in all users when used. Most orgs do not have multiple hundreds of users so heap size shouldn't be a concern. But if it is you can just modify the queryUsers() method to only return active users or filter down based on other criteria.
public class UserUtil {
//Protected Members
private static final UserUtil instance = new UserUtil();
private Map<Id, User> mapUsers;
//Properties
public static User CurrentUser {
get { return getUser(UserInfo.getUserId()); }
}
//Constructor
private UserUtil() {
mapUsers = new Map<Id, User>(queryUsers());
}
//Public Methods
public static User getUser(Id userId) {
if (instance.mapUsers.containsKey(userId)) {
return instance.mapUsers.get(userId);
}
else {
throw new InvalidUserIdException('Unable to locate user id: ' + userId);
}
}
//Private Methods
private List<User> queryUsers() {
return [SELECT
Id
, Name
, UserName
, Email
, Alias
FROM
User];
}
//Internal Classes
public class InvalidUserIdException extends Exception {}
}
I wrote my own utility class. I'm still interested in better techniques though.
This utility class lazy loads when the first property is accessed. Update_Closed_Won_Opportunities__c and Set_Opportunities_to_Closed_Won__c are my custom fields on the User object (visible only to System Administrators so people can't upgrade their permissions).
public with sharing class MyUserInfo {
private Id userId;
private User myUser; // Hold onto the user object once we've loaded it
// Default constructor uses the active user id
public MyUserInfo() {
userId = UserInfo.getUserId();
}
// Secondary constructor accepts a user id as a parameter
public MyUserInfo(Id someOtherUserId) {
userId = someOtherUserId;
}
// Only called one time when we first need it so grab all of the custom fields now
private void LazyLoadUser() {
System.AssertNotEquals(null, userId);
myUser = [
SELECT Update_Closed_Won_Opportunities__c, Set_Opportunities_To_Closed_Won__c
FROM User
WHERE id = :userId
];
System.AssertNotEquals(null, myUser, 'Unable to load user with id ' + userId); // could return defaults instead
}
// Getters (be sure to include each field in the SOQL of LazyLoadUser)
public boolean UpdateClosedWonOpportunities { get {
if (myUser == null) LazyLoadUser();
return myUser.Update_Closed_Won_Opportunities__c;
} }
public boolean SetOpportunitiesToClosedWon { get {
if (myUser == null) LazyLoadUser();
return myUser.Set_Opportunities_To_Closed_Won__c;
} }
}
Here is my trigger utilizing that class. The first line myUserInfo = new MyUserInfo(); doesn't run any SOQL. That won't happen until the first custom get property is used. Subsequent calls don't need SOQL.
trigger LockClosedOpportunity on Opportunity (before update) {
MyUserInfo myUserInfo = new MyUserInfo();
for (Opportunity o : trigger.old)
{
if (!myUserInfo.UpdateClosedWonOpportunities && o.StageName == 'Closed Won')
trigger.newMap.get(o.Id).addError('You do not have permission to change an Opportunity after it has been set to Closed Won.');
}
for (Opportunity o : trigger.new)
{
if ( !myUserInfo.SetOpportunitiesToClosedWon && o.StageName == 'Closed Won' && trigger.oldMap.get(o.Id).StageName != 'Closed Won' )
o.addError('You do not have permission to set an Opportunity to Closed Won.');
}
}
It reads similar to $User in formulas and I don't have to worry about tacking on multiple SOQL calls when one (or zero) suffices.

Resources