I have this piece of code which adds an entry to film_actor (in theory), but it doesn't work. This code doesn't crash, but doesn't save the added entry to the database.
Actor nuevo = ActorFacadeEJB.find(actor_id);
Film pelicula = FilmFacadeEJB.find(film_id);
pelicula.getActors().add(nuevo);
Also I have this code:
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "film_actor", joinColumns = { #JoinColumn(name = "film_id") }, inverseJoinColumns = {
#JoinColumn(name = "actor_id") })
private Set<Actor> actors;
public Set<Actor> getActors() {
return actors;
}
The actor also has a film set:
#ManyToMany(mappedBy="actors")
private Set<Film> film= new HashSet<Film>();
How can I fix all this to make it work? I googled it, and many people have similar code to mine, but just mine doesn't work.
Make sure your Annotations are proper as below :
#Entity
#Table(name="film")
class Film{
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name="film_actor",
joinColumns={#JoinColumn(name="film_id")},
inverseJoinColumns={#JoinColumn(name="actor_id")})
private Set<Actor> actors=new HashSet<Actor>;
}
#Entity
#Table(name="actor")
class Actor{
#ManyToMany(mappedBy="actors")
private Set<Film> film= new HashSet<Film>();
}
You need to assign both sides of the relationship:
pelicula.getActors().add(nuevo);
nuevo.getFilms().add(pelĂcula);
Related
I have a Many-to-Many relationship with User and Role JPA entities. When I try to save the entities, both User and Role entities gets persisted in the table, but the junction table is not getting inserted with the records, Where am I going wrong
User Entity
#Entity
#Table(name="users")
#Data
#NoArgsConstructor
#EqualsAndHashCode(exclude = "roles")
#ToString(exclude = "roles")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String password;
private double salary;
public User(String name, String password, double salary) {
super();
this.name = name;
this.password = password;
this.salary = salary;
}
#ManyToMany(
mappedBy = "users")
private Set<Role> roles = new HashSet<>();
public void addRole(Role role) {
this.roles.add(role);
role.getUsers().add(this);
}
}
Role Entity
#Entity
#Table(name = "roles")
#Data
#NoArgsConstructor
#EqualsAndHashCode(exclude = "users")
#ToString(exclude = "users")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String roleName;
public Role(String roleName) {
super();
this.roleName = roleName;
}
#ManyToMany
#JoinTable(
name="user_roles",
joinColumns = #JoinColumn(name="role_id", nullable = false),
inverseJoinColumns = #JoinColumn(name="user_id", nullable = false)
)
private Set<User> users = new HashSet<>();
}
Client class
#EventListener(ApplicationReadyEvent.class)
public void onApplicationStartup(ApplicationReadyEvent event) {
User kiran = new User("kiran", this.passwordEncoder.encode("welcome"), 4500000);
User vinay = new User("vinay", this.passwordEncoder.encode("welcome"), 4500000);
Role userRole = new Role("ROLE_USER");
Role adminRole = new Role("ROLE_ADMIN");
kiran.addRole(userRole);
vinay.addRole(userRole);
vinay.addRole(adminRole);
this.userRepository.save(kiran);
this.userRepository.save(vinay);
}
Where am I going wrong?
You've mapped a bidirectional relationship, but are only setting one side of it in your object model - the wrong side. Should there ever be a discrepancy, the owning side controls the values of foreign keys, and since you have left the owning side empty, they aren't being set. You are responsible to set both sides of relationships and keeping them in synch with what you want in the database.
Since you don't have cascade options set on the relationships, you are also responsible for persisting the roles independently from the Users. Something more like:
public void onApplicationStartup(ApplicationReadyEvent event) {
// you might want to check to see if these roles already exists and use those instead of creating new ones
Role userRole = roleRepository.save(new Role("ROLE_USER"));
Role adminRole = roleRepository.save(new Role("ROLE_ADMIN"));
User kiran = new User("kiran", this.passwordEncoder.encode("welcome"), 4500000);
kiran.addRole(userRole);//assumes this adds the user to the role.users as well.
this.userRepository.save(kiran);
User vinay = new User("vinay", this.passwordEncoder.encode("welcome"), 4500000);
vinay.addRole(userRole);
vinay.addRole(adminRole);
this.userRepository.save(vinay);
}
Also, you are using Set in your entities with Lombok using "#EqualsAndHashCode" generation. Don't do that!
Set uses the equals/hashcode logic to determine if two objects are the same to filter out duplicates, while Lombok generates those methods to use what are mutable fields. In the case you have new entities in those sets (ie this usecase), the IDs are null and will change when set from JPA. You are better off keeping Java equals/hashcode logic if you don't know what effects those will have on your application. try using either List in your model and/or not having Lombok generate those method for you.
Here below is a simple model for a pet shop...
Pet Class
#Entity
#Table(name = "pet")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public abstract class Pet {
#Column(name = "id", nullable = false)
private Long id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "birth_date", nullable = false)
private LocalDate birthDate;
#Column(name = "death_date")
private LocalDate deathDate;
#ManyToOne
#JoinColumn(name = "pet_shop_id", nullable = false, referencedColumnName = "id")
#Setter(AccessLevel.NONE)
private PetShop petShop;
public void setPetShop(PetShop petShop) {
setPetShop(petShop, true);
}
public void setPetShop(PetShop petShop, boolean add) {
this.petShop= petShop;
if (petShop!= null && add) {
petShop.addPet(this, false);
}
}
PetShop Class
#Entity
#Table(name = "pet_shop")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public class PetShop {
#Column(name = "id", nullable = false)
private Long id;
...
#OneToMany(
mappedBy = "petShop",
fetch = FetchType.LAZY,
cascade = {CascadeType.ALL})
private List<Pet> pets= new ArrayList<>();
public void addPet(final Pet pet) {
addPet(pet, true);
}
public void addPet(final Pet pet, boolean set) {
if (pet!= null) {
if (pets.contains(pet)) {
pets.set(pets.indexOf(pet), pet);
} else {
pets.add(pet);
}
if (set) {
pet.setPetShop(this, false);
}
}
}
}
PetShopRepository Interface
public interface PetShopRepository
extends JpaRepository<PetShop, Long> {
#Query(
"SELECT DISTINCT ps FROM PetShop ps"
+ " JOIN ps.pets p"
+ " WHERE ps.id = :id AND p.deathDate IS NULL")
#Override
Optional<PetShop> findById(#NonNull Long id);
}
... and here is how to create a PetShop with 2 Pet instances (one alive and another one dead):
final Pet alive = new Pet();
alive.setName("cat");
alive.setCall("meow");
alive.setBirthDate(LocalDate.now());
final Pet dead = new Pet();
dead.setName("cat");
dead.setCall("meow");
dead.setBirthDate(LocalDate.now().minusYears(15L));
dead.setDeathDate(LocalDate.now());
final PetShop petShop = new PetShop();
petShop.getPets().add(alive);
petShop.getPets().add(dead);
petShopRepositiry.save(petShop);
Now I want to retrieve the PetShop and I'd assume it contains only pets that are alive:
final PetShop petShop = petShopRepository.findById(shopId)
.orElseThrow(() -> new ShopNotFoundException(shopId));
final int petCount = petShop.getPets().size(); // expected 1, but is 2
According to my custom query in PetShopRepository I'd expect petShop.getPets() returns a list with 1 element, but it actually returns a list with 2 elements (it includes also the dead pet).
Am I missing something? Any hint would be really appreciated :-)
This is because Jpa maintains the coherence of the relations despite your query.
I.e. : your query returns the shops having at least one pet alive. But, Jpa will return the shop with the complete set of pets. And you can probably see extra sql queries sent by Jpa (if you set show_sql=true) to refill pets collection on the returned shop.
Fundamently, it's not because you wanted to get the shops with living pets that these shops loose their dead pets.
To get it right you would have to design the pets collection so that it would filter the dead pets. Hibernate provides such annotations (#Filter and #FilterDef), but apparently JPA does not.
I don't think that filtering at #Postload would be a good idea, because you would have to put back the filtered dead pets in the collection before any flush in the database. That looks risky to me.
I've seen other posts about this problem, but have found no answer to my own troubles. I have
#Entity
#Table(name= ServerSpringConstants.COMPANY)
public class Company implements Serializable {
private static final long serialVersionUID = -9104996853272739161L;
#Id #GeneratedValue(strategy=GenerationType.AUTO)
#Column (name = "companyID")
private long companyID;
#OneToMany (targetEntity = Division.class, cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.REFRESH},
fetch = FetchType.EAGER)
#JoinTable (name = "companyDivisionJoinTable",
joinColumns = #JoinColumn(name="companyID"),
inverseJoinColumns = #JoinColumn(name="divisionID")
)
private Set<Division> divisions = new HashSet<>();
public long getCompanyID() {
return companyID;
}
public Set<Division> getDivisions() {
return divisions;
}
public void setDivisions(Set<Division> divisions) {
this.divisions = divisions;
}
}
On the other side:
#Entity
#Table(name= ServerSpringConstants.DIVISION)
public class Division implements Serializable {
private static final long serialVersionUID = -3685914604737207530L;
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
#Column(name = "divisionID")
private long divisionID;
#ManyToOne
(fetch = FetchType.LAZY, optional = false, targetEntity = Company.class,
cascade = {CascadeType.PERSIST, CascadeType.MERGE
}
)
#JoinColumn(name="companyID", referencedColumnName = "companyID")
private Company company;
public long getDivisionID() {
return divisionID;
}
public void setDivisionID(long divisionID) {
this.divisionID = divisionID;
}
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
Yet for some reason, LAZY loading not working. I'm using JPA. I'm calling back the companies, and their enclosing divisions from within a 'User' class -- the pertinent part
#ManyToMany (targetEntity = Company.class,
cascade={
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.REFRESH},
fetch=FetchType.EAGER )
#JoinTable (
name="companyUserJoinTable",
joinColumns=#JoinColumn(name="userID"),
inverseJoinColumns=#JoinColumn(name="companyID")
)
private Set<Company> company = new HashSet<>();
I've searched out existing threads, and have tried adding various suggestions, but nothing has helped!
Any help appreciated.
Thanks!
Since you are loading the divisions set eagerly (with fetch = FetchType.EAGER) and you have a bidirectional association, divisions will be initialized with the parent reference to company. I can't see any problem with it. Jpa loaded the full object tree because you just told it so. A company contains divisions which contain a back reference to the company that loaded them.
To understand it better, since the reason for lazy loading is to reduce the data loaded from database, the owning company is already loaded in session for the divisions, so why not setting the association too?
The #ManyToOne association on the other side takes effect if you load divisions first.
To be more correct with your mapping add also a #MappedBy attribute to the one part of the association. This does not affect fetching behavior but will prevent double updates to the database issued by both ends of the association.
I have three database tables: Customer, Product and PurchaseOrder (for mapping). I am using openjpa for peristence in java rest application.
To all of the tables I have corresponding entities:
Customer
#Entity
#Table(name = "customer")
#XmlRootElement
#NamedQueries({...})
public class Customer implements Serializable {
...
#OneToMany(cascade = CascadeType.ALL, mappedBy = "customerId")
private Collection<PurchaseOrder> purchaseOrderCollection;
Product
#Entity
#Table(name = "product")
#XmlRootElement
#NamedQueries({...})
public class Product implements Serializable {
...
#OneToMany(cascade = CascadeType.ALL, mappedBy = "productId")
private Collection<PurchaseOrder> purchaseOrderCollection;
PurchaseOrder
#Entity
#Table(name = "purchase_order")
#XmlRootElement
#NamedQueries({..})
public class PurchaseOrder implements Serializable {
...
#Id
#Basic(optional = false)
#Column(name = "order_num")
private Integer orderNum;
#JoinColumn(name = "customer_id", referencedColumnName = "customer_id")
#ManyToOne(optional = false)
private Customer customer;
#JoinColumn(name = "product_id", referencedColumnName = "product_id")
#ManyToOne(optional = false)
private Product product;
What is the best way to get all the customers who ordered a product with specific id?
I could create namedQuery, I could build criteria with joins etc. But i think there could be a better way how to make use of the mapping entity (what would be point of this entity otherway?). Something like setting the productId to the purchaseOrder entity and then fetch all the customers via purchaseOrderCollection in customer entity? But i cannot figure it out. Is there other way than custom/named query or criteria building?
Thanks.
ok I figured it out, it can be this way
long productId = //get the id
Product product = entityManager.find(Product.class, productId);
Collection<PurchaseOrder> purchaseOrderCollection = product.getPurchaseOrderCollection();
if (purchaseOrderCollection != null) {
List<Integer> customers = new ArrayList<>(product.getPurchaseOrderCollection().size());
for (PurchaseOrder purchaseOrder : product.getPurchaseOrderCollection()) {
customers.add(purchaseOrder.getCustomerId());
}
return customers;
} else {
return Collections.EMPTY_LIST; // or null;
}
feel free to offer better sollution :)
I have Two Entities , Entity Applicants, Entity JobApplications. The Relationship is
//Entity Applicants
public class AppPers implements Serializable {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "appPers",fetch = FetchType.EAGER)
private Collection<JobApplications> jobApplicationsCollection;
.........................
}
//Entity JobApplications
public class JobApplications implements Serializable {
#JoinColumn(name = "app_id", referencedColumnName = "APP_ID", insertable = false, updatable = false)
#ManyToOne(optional = false,fetch = FetchType.EAGER)
private AppPers appPers;
So after i do a merger
AppPers ap =em.find(AppPers.class,appId);
ap.setJobApplicationsCollection(c);
em.merge(ap);
em.refresh(ap);
And try to execute
//fetch the newly created list
List<JobApplications> list = new ArrayList<JobApplications>();
list = em.createNamedQuery("JobApplications.findAll").getResultList();
//list size is 3 in my case
for( JobApplications ja: list) {
String a = ja.getAppPers().getSurName();
}
surName being a column of AppPers, I get NullPointer Exception. This happens untill i restart Glassfish then the Null Pointer Does not Happen. I have tried setting the fetch to Eager but still no joy. What could i be missing
I don't see how this relates to JPA. It's plain old Java. You have a class defined as
public class JobApplications implements Serializable {
private AppPers appPers;
...
}
The default value of a field is null.
So if you do
JobApplications ja = new JobApplications ();
AppPers ap = ja.getAppPers();
ap will be null. So if you do
JobApplications ja = new JobApplications ();
String a = ja.getAppPers().getSurName();
you'll get a NullPointerException.