Adding JPA Non primary key referenced ManyToOne entity - java

public class DemandItem extends BaseEntity{
#ManyToOne(cascade = {})
#JoinColumn(name = "sku_code",referencedColumnName = "sku_code", nullable = false)
private Inventory inventory;
public Inventory getInventory() {
return inventory;
}
public void setInventory(Inventory inventory) {
this.inventory = inventory;
}
public void setSkuCode(String skuCode){
if(this.inventory == null){
this.setInventory(new Inventory(skuCode));
}
}
public String getSkuCode(){
if(this.inventory != null){
return this.inventory.getSkuCode();
}
return null;
}
}
Now for creating a DemandItem I should first Load the inventory using skuCode and then set the inventory using setInventory. Just calling setSkuCode function causing transient entity exception. How to solve it,
I need to just get the skuCode filled while creating the data, but i dont need to load it while creating this entity.
While fetching I need the Inventory entity to be loaded.

Related

Android Room - How can I check if an entity with the same name already exists before inserting?

I'm creating an app using the mvvm pattern with android room, but I've ran into some trouble validating user input. When a user wants to add an ingredient to the app, they are required to enter a name for this ingredient. I want the app to notify the user if the name is already in use. I have tried some stuff using the Transformations.Map() functions but without any success.
I'm fairly new to the mvvm pattern and LiveData, and I've been stuck on this for quite a while now so any advice would be appreciated.
This is the ingredient entity:
#Entity(tableName = "ingredient")
public class BaseIngredient {
#PrimaryKey(autoGenerate = true)
private int id;
private String name;
private String category;
#ColumnInfo(name = "cooking_time")
private int cookingTime;
#Ignore
public BaseIngredient() {
}
public BaseIngredient(int id, #NonNull String name, #NonNull String category, int cookingTime)
throws InvalidValueException {
this.id = id;
setName(name);
setCookingTime(cookingTime);
setCategory(category);
}
public void setName(String name) throws InvalidNameException {
if (name == null || name.isEmpty())
throw new InvalidNameException("Name is empty");
if (!name.matches("[A-z0-9]+( [A-z0-9]+)*"))
throw new InvalidNameException("Name contains invalid tokens");
this.name = name;
}
public void setCategory(String category) throws InvalidCategoryException {
if (category == null || category.isEmpty())
throw new InvalidCategoryException("Category is empty");
if (!category.matches("[A-z0-9]+"))
throw new InvalidCategoryException("Category contains invalid tokens");
this.category = category;
}
public void setCookingTime(int cookingTime) throws InvalidCookingTimeException {
if (cookingTime < 1)
throw new InvalidCookingTimeException("Time must be positive");
this.cookingTime = cookingTime;
}
/* getters */
public boolean isValid() {
return name != null && category != null && cookingTime != 0;
}
This is the IngredientRepository I'm using:
private IngredientDao ingredientDao;
private LiveData<List<BaseIngredient>> ingredients;
public IngredientRepository(Application application) {
LmcfyDatabase database = LmcfyDatabase.getDatabase(application.getApplicationContext());
ingredientDao = database.ingredientDao();
ingredients = ingredientDao.getAllIngredients();
}
public LiveData<List<BaseIngredient>> getAllIngredients() {
return ingredients;
}
public LiveData<List<BaseIngredient>> getIngredientsWithQuery(String query) {
return ingredientDao.getIngredientsWithQuery("%" + query + "%");
}
public void insert(BaseIngredient ingredient) {
LmcfyDatabase.databaseWriteExecutor.execute(() -> {
ingredientDao.insert(ingredient);
});
}
public LiveData<Integer> getIngredientsWithNameCount(String name) {
return ingredientDao.getIngredientsWithNameCount(name);
}
The IngredientDao:
#Insert(onConflict = OnConflictStrategy.IGNORE, entity = BaseIngredient.class)
long insert(BaseIngredient ingredient);
#Delete(entity = BaseIngredient.class)
void delete(BaseIngredient ingredient);
#Query("SELECT * FROM ingredient")
LiveData<List<BaseIngredient>> getAllIngredients();
#Query("SELECT * FROM ingredient WHERE name LIKE :query")
LiveData<List<BaseIngredient>> getIngredientsWithQuery(String query);
#Query("SELECT COUNT(id) FROM ingredient WHERE name LIKE :name")
LiveData<Integer> getIngredientsWithNameCount(String name);
And finally the ViewModel that is used to create an Ingredient
private final IngredientRepository repository;
private final BaseIngredient ingredient;
private final MutableLiveData<String> nameError;
private final MutableLiveData<String> categoryError;
private final MutableLiveData<String> cookingTimeError;
private final MutableLiveData<Boolean> ingredientValidStatus;
public AddIngredientViewModel(#NonNull Application application) {
super(application);
repository = new IngredientRepository(application);
ingredient = new BaseIngredient();
nameError = new MutableLiveData<>();
categoryError = new MutableLiveData<>();
cookingTimeError = new MutableLiveData<>();
ingredientValidStatus = new MutableLiveData<>();
}
public void onNameEntered(String name) {
try {
ingredient.setName(name);
nameError.setValue(null);
} catch (InvalidNameException e) {
nameError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
public void onCategoryEntered(String category) {
try {
ingredient.setCategory(category);
categoryError.setValue(null);
} catch (InvalidCategoryException e) {
categoryError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
public void onCookingTimeEntered(int cookingTime) {
try {
ingredient.setCookingTime(cookingTime);
cookingTimeError.setValue(null);
} catch (InvalidCookingTimeException e) {
cookingTimeError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
private void updateIngredientValid() {
ingredientValidStatus.setValue(ingredient.isValid());
}
public boolean saveIngredient() {
if (ingredient.isValid()) {
Log.d(getClass().getName(), "saveIngredient: Ingredient is valid");
repository.insert(ingredient);
return true;
} else {
Log.d(getClass().getName(), "saveIngredient: Ingredient is invalid");
return false;
}
}
The onXXEntered() functions in the viewmodel are linked to the textViews in the fragment, and the saveIngredient() function is called when a save button is pressed. The XXError LiveData's are used to display errors to the user.
The real problem lies in the fact that LiveData is async, and the user can change their input and click the save button before the LiveData contains the result from the database. If I want the check the input upon saving it, the 'add activity' will have already finished before the check is done.
Again, any help would be very much appreciated.
I had to do something similar in one of my recent projects. What I did was:
Room cannot create columns with SQLite Unique constraint (if it is not the PrimaryKey - which is your case). So don't initialize the database in your app code using Room. Instead, create a database file outside your application. Add a Unique constraint on the 'name' column. Then add the database file in your project under assets folder. (for example, create a subfolder inside assets - 'db_files' - and copy your pre-created database file under this folder)
I guess you use Singleton pattern for your #DataBase class. Replace your 'getInstance()' method with following:
public static MyDB getInstance(final Context context) {
if(INSTANCE == null) {
synchronized (AVListDB.class) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
MyDB.class,"myDB.db")
.createFromAsset( "db/myDB.db")
.build();
}
}
return INSTANCE;
}
This creates a copy of your pre-packaged database file under applications database files path.
With unique constraint in place, your #Insert and #Update annotated methods will respect the constraint condition, and will throw a SQLiteConstraintException if you try to insert a previously used name. You can catch this exception, and pass it to your View or ViewModel as you wish (I implemented a simple centralized event publisher component).
I hope that helps.

How can I do findBy operations with attributes not directly on the entity hibernate spring JPA

I have three entities with the following relationship
Entity User has many Activities (Unidirectional relationship) with join table because I need relationship attributes,
Rule entity has many activities (bidirectional relationship)
Can I do a findBy that search Activities that are from one specific rule?
it sounds possible because Activities has rule but I don't know how to do this with Hibernate
This is my code from both relationships
EDIT: I've tested with MySQL and the query that I need is this
select * from user_activity where user_id = userIdValue and activity_id in (
select id from activity where rule_id = rule_idValue);
how can I do that query with hibernate on spring?
User-Activity (With join table for relationship attributes)
#Entity
#Table(name="USER_ACTIVITY")
public class JoinedUserActivity {
public JoinedUserActivity() {}
public JoinedUserActivity(JoinedUserActivityId joinedUserActivityId, String fileName) {
this.joinedUserActivityId = joinedUserActivityId;
this.fileName = fileName;
this.score = 0;
this.lastHint = 0;
}
private String fileName; //user activity filename
#ManyToOne
#JoinColumn(name="ACTIVITY_ID", insertable=false, updatable=false)
private Activity activity;
private Integer score;
private Integer lastHint;
public Activity getActivity() {
return activity;
}
public void setActivity(Activity activity) {
this.activity = activity;
}
//more set and gets
#EmbeddedId
private JoinedUserActivityId joinedUserActivityId;
// required because JoinedUserAchievments contains composite id
#Embeddable
public static class JoinedUserActivityId implements Serializable {
/**
*
*/
private static final long serialVersionUID = -9180674903145773104L;
#ManyToOne
#JoinColumn(name="USER_ID")
private User user;
#ManyToOne
#JoinColumn(name="ACTIVITY_ID")
private Activity activity;
// required no arg constructor
public JoinedUserActivityId() {}
public JoinedUserActivityId(User user, Activity activity) {
this.user = user;
this.activity = activity;
}
public JoinedUserActivityId(Integer userId, Integer activityId) {
this(new User(userId), new Activity(activityId));
}
public User getUser() {
return user;
}
public Activity getActivity() {
return activity;
}
public void setUser(User user) {
this.user = user;
}
public void setActivity(Activity activity) {
this.activity = activity;
}
#Override
public boolean equals(Object instance) {
if (instance == null)
return false;
if (!(instance instanceof JoinedUserActivityId))
return false;
final JoinedUserActivityId other = (JoinedUserActivityId) instance;
if (!(user.getId().equals(other.getUser().getId())))
return false;
if (!(activity.getId().equals(other.getActivity().getId())))
return false;
return true;
}
#Override
public int hashCode() {
int hash = 7;
hash = 47 * hash + (this.user != null ? this.user.hashCode() : 0);
hash = 47 * hash + (this.activity != null ? this.activity.hashCode() : 0);
return hash;
}
}
}
User
#OneToMany(cascade=CascadeType.ALL, mappedBy="joinedUserActivityId.user")
private List<JoinedUserActivity> joinedUserActivityList = new ArrayList<JoinedUserActivity>();
Activity has no info about User because is unidirectional.
Now my Activity-rule has no join table and bidirectional with the following code
Activity.java
#JsonIgnore
#ManyToOne
Rule rule;
Rule.java
#OneToMany(cascade=CascadeType.ALL, mappedBy="rule")
private List<Activity> ListActivities = new ArrayList<Activity>();
so... on join table user_activity, can I define a findBy function that returns which activities has one user from one rule?
I've managed to do
#Override
JoinedUserActivity findOne(JoinedUserActivityId id);
this returns one Activity from one user
but I don't know how to filter by Rule
You subquery isn't needed. All you need is a join.
In JPQL:
select distinct jua from JoinedUserActivity jua
where jua.user.id = :userId
and jua.activity.rule.id = :ruleId
Or, using explicit joins:
select distinct jua from JoinedUserActivity jua
join jua.user u
join jua.activity a
join a.rule r
where u.id = :userId
and r.id = :ruleId
Now just add the method you want in your Spring-data repositoty interface, and annotated it with #Query, with one of the above queries as value.

Hibernate call DELETE from table after method end

I have problem, and I don't know how to solve it.
I have entity:
#Entity
#Table(name = "entity_languagetree")
#AttributeOverride(name = "id", column = #Column(name = "languagetree_id"))
public class LanguageTree extends BaseObject {
#ElementCollection(targetClass = java.lang.String.class, fetch = FetchType.EAGER)
#CollectionTable(name = "view_languagetree_to_stringlist")
private List<String> relationship = new ArrayList<>();
public LanguageTree() {
//
}
public List<String> getRelationship() {
return relationship;
}
public void setRelationship(List<String> relationship) {
this.relationship = relationship;
}
}
where BaseObject is
#MappedSuperclass
public class BaseObject {
#Id
#GeneratedValue
#Column(name = "entity_id")
private Long id;
/**
*
* #return true if the entity hasn't been persisted yet
*/
#Transient
public boolean isNew() {
return id == null;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Bean getBean() {
return null;
}
}
Work with object - in my servlet, I am calling jsVarTree() like this:
String var = jsVarTree();
My problem is, that after method jsVarTree is finished, hibernate delete my relationship list from entity LanguageTree. I don't know why! I am not calling any delete and etc.. (I AM SURE, I SPENT A LOT OF TIME IN DEBUGER!)
:
#Override
public String jsVarTree() {
TreeBuilder tb = new TreeBuilder(getLanguageList());
return tb.getJsVarString(); // THIS METHOD IS ONLY GETTER !!!!
}
#Override
public List<String> getLanguageList() {
LanguageTree lt = getLanguageTreeObject();
return lt.getRelationship();
}
#Override
public LanguageTree getLanguageTreeObject() {
long fakingId = languageTreeDao.getLastId();
ServerLogger.logDebug("LAST FAKING ID: " +fakingId);
return languageTreeDao.findOne(fakingId);
}
I found this log in loggor:
HibernateLog --> 15:01:03 DEBUG org.hibernate.SQL - delete from
view_languagetree_to_stringlist where LanguageTree_languagetree_id=?
Can somebody tell me, why hibernate call delete over my table?
I saw a table in phpmyadmin..
TABLE IS FULL.
String var = jsVarTree();
TABLE IS EMPTY.
Table is deleted after return tb.getJsVarString(); is finished.
Thank you for any help!

detached entity passed to persist when Composite key used with entity

i'm getting error like
org.springframework.dao.InvalidDataAccessApiUsageException: detached
entity passed to persist: com.websopti.wotms.entity.Project; nested
exception is org.hibernate.PersistentObjectException: detached entity
passed to persist: com.websopti.wotms.entity.Project
i have Composite key join on entity basically i have two entity one is Project and one is User and i have created composite key join between them by making another Entity called ProjectUser following are classes
User
public class User extends BaseEntity<Long> implements UserDetails {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Long id;
...
#OneToMany(mappedBy="user",fetch=FetchType.LAZY)
private List<ProjectUser> userProjects;
...
getter and setters
}
Project
public class Project extends BaseEntity<Long> {
private static final long serialVersionUID = 7541005803335530236L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
...
#OneToMany(mappedBy="project",fetch=FetchType.LAZY, cascade=CascadeType.ALL)
private List<ProjectUser> projectUsers;
...
}
ProjectUser
#IdClass(CompositeKey.class)
public class ProjectUser extends BaseEntity<Long> {
private static final long serialVersionUID = 476483195548055916L;
#ManyToOne
#Id
#JoinColumn(name="user_id", referencedColumnName="id")
private User user;
#ManyToOne
#Id
#JoinColumn(name="project_id", referencedColumnName="id")
private Project project;
private ProjectRole role;
...
getter setter
}
CompositeKey
public class CompositeKey implements Serializable {
private static final long serialVersionUID = 2186028617883601307L;
private long user;
private long project;
public CompositeKey() {}
public CompositeKey(long user, long project) {
this.user = user;
this.project = project;
}
public boolean equals(Object object) {
if (object instanceof CompositeKey) {
CompositeKey pk = (CompositeKey)object;
return user == pk.user && project == pk.project;
} else {
return false;
}
}
...
getter setter
}
now when i create project at that time if i set List object and save then it works fine but when i wanted to update that project and set Modified List object that is manually created by code and set to project object. so now when i try to save this modified project object then i get error for "detached entity passed to persist".
i'm doing like following and saving this project object
my code for saving project object controller method like follow
#RequestMapping(value="/update", method=RequestMethod.POST)
public String updateProject(#ModelAttribute("project") Project project, HttpServletRequest request, RedirectAttributes redirectAttributes){
try {
project.setIpAddress(CommonUtils.getClientIpAddr(request));
Project oldProject = projectService.findById(project.getId());
List<ProjectUser> newProjectUsers = new ArrayList<ProjectUser>();
List<Integer> selectedIndexes = project.getSelectedRoleIndexes();
List<User> users = project.getTeam();
if(users != null && users.size() > 0){
for (User u : users) {
newProjectUsers.add(new ProjectUser(u, project, ProjectRole.getRole(selectedIndexes.get(users.indexOf(u)))));
}
}
List<ProjectUser> oldProjectUsers = oldProject.getProjectUsers();
for (ProjectUser projectUser : new ArrayList<>(oldProjectUsers)) {
if(!users.contains(projectUser.getUser())){
/*disable all task for user*/
//taskService.disableUserTaskForProject(projectUser.getUser(), oldProject);
/*send remove member form project mail*/
//projectEmailService.sendProjectTeamMemberRemoveEmail(projectUser.getUser(), oldProject);
oldProjectUsers.remove(projectUser);
}else{
ProjectUser pu = newProjectUsers.get(users.indexOf(projectUser.getUser()));
oldProjectUsers.remove(projectUser);
projectUser.setRole(pu.getRole());
oldProjectUsers.add(projectUser);
}
}
List<User> oldTeam = oldProjectUsers.stream().map(pu -> {return pu.getUser();}).collect(Collectors.toList());
for (ProjectUser projectUser : newProjectUsers) {
if(!oldTeam.contains(projectUser.getUser())){
/*send user add in project mail*/
//projectEmailService.sendProjectTeamMemberAddEmail(projectUser.getUser(), oldProject);
oldProjectUsers.add(projectUser);
}
}
//oldProjectUsers = entityManager.merge(oldProjectUsers);
//projectUserService.updateAllProjectUsers(oldProjectUsers);
/*for (ProjectUser projectUser : oldProjectUsers) {
entityManager.merge(projectUser);
}*/
project.setProjectUsers(oldProjectUsers);
//project = entityManager.merge(project);
project = projectService.update(project);
/*old team except admin*/
/*List<User> oldTeam = oldProject.getProjectUsers()
.stream()
.map(pu -> {return pu.getUser();})
.collect(Collectors.toList());
List<User> newTeam = project.getTeam()
.stream()
.filter(u -> u.getRole() != SystemRole.ADMIN)
.collect(Collectors.toList());
project = projectService.update(project);
for (User user : oldTeam) {
if(!newTeam.contains(user)){
disable all task for user
taskService.disableUserTaskForProject(user, project);
send remove member form project mail
projectEmailService.sendProjectTeamMemberRemoveEmail(user, project);
}
}
for (User user : newTeam) {
if(!oldTeam.contains(user)){
send user add in project mail
projectEmailService.sendProjectTeamMemberAddEmail(user, project);
}
}*/
} catch(Exception e) {
e.printStackTrace();
return "redirect:/user/UserDashboard";
}
redirectAttributes.addFlashAttribute("projectId",project.getId());
redirectAttributes.addFlashAttribute("fromUpdate", true);
return "redirect:/user/"+PageTemplate.userDashboard;
}
please help me i'm stuck here
first of all thanks hansnae for giving me confidence to work on that question because i have visited that question 3 times and go through that question also have thinked to apply that solution but not applied because of i'm not sure about that.
now that bidirectional relation ship issue is there in my case so i have applied that logic for modification in List object and it worked for me
JPA/Hibernate: detached entity passed to persist
but in that question remove object facility not worked for me because i have used composite key join in my case i haev to work extra to remove that object
when i try to save removed object and set null in related joined entity then my case it throws Exception
No part of a composite identifier may be null
so to remove that object from list i have to manually remove that object by repository delete query.
Thanks for help and hands up too this great community

hibernate joins not working

I am new in hibernate.i have 2 two tables one is job and other is jobSkills. but when i insert data into database using jsp form.all data is going properly but jobID is going null
i know its simple but i don't know how to solve it.
please help me
code in job class :
#OneToMany(mappedBy="job",cascade = CascadeType.ALL)
private List <JobSkill> jobSkill;
public List<JobSkill> getJobSkill() {
return jobwalkintime;
}
public void setJobSkill(List<JobSkill> jobSkill) {
this.jobSkill = jobSkill;
}
code in JobSkill class :
#ManyToOne
#JoinColumn(name = "JobId")
private Job job;
I would really guess problem is with your Set Accessor method i..e set methods in both JobSkill and Job. Change your setJobSkill method as below
#OneToMany(mappedBy="job",cascade = CascadeType.ALL)
private List <JobSkill> jobSkill = new ArrayList<JobSkill>();
public List<JobSkill> getJobSkill() {
return jobwalkintime;
}
public void setJobSkill(List<JobSkill> jobSkill) {
if (jobSkill != null ) {
for (JobSkill js : jobSkill) {
js.setJob(this);
}
this.jobSkill = jobSkill;
}
In JPA world JobSkill will be owner of relation, so you set Job instance on each JobSkill to establish relation b/w them.
In General , for Bi-directional one-to-many two way relation , Accessor method should like below and assume entities are Customer and Order and one-to-many relation b/w them.
Customer.java
public Collection<Order> getOrders() {
return Collections.unmodifiableCollection(orders);
}
public void addToOrders(Order value) {
if (!orders.contains(value)) {
orders.add(value);
value.setCustomer(this);
}
}
public void removeFromOrders(Order value) {
if (orders.remove(value)) {
value.setCustomer(null);
}
}
Order.java
public void setCustomer(Customer value) {
if (this.customer != value) {
if (this.customer != null) {
this.customer.removeFromOrders(this);
}
this.customer = value;
if (value != null) {
value.addToOrders(this);
}
}
}
public Customer getCustomer() {
return customer;
}

Categories