I have the following objects:
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Entity(name="Group")
public class Group {
#Id
#GeneratedValue
#NotNull
#Column(name = "GROUP_ID")
private Long id;
#Column(name="NAME")
private String name;
#OneToMany(
targetEntity = Product.class,
mappedBy = "groupId",
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true
)
private List<Product> products = new ArrayList<>();
public Group(String name) {
this.name = name;
}
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity(name="Product")
public class Product {
#Id
#GeneratedValue
#NotNull
#Column(name="PRODUCT_ID")
private Long id;
#Column(name="NAME")
private String name;
#Column(name="DESCRIPTION")
private String description;
#Column(name="PRICE")
private double price;
#ManyToMany
#JoinTable(
name = "JOIN_PRODUCT_CART",
joinColumns = {#JoinColumn(name = "PRODUCT_ID", referencedColumnName = "PRODUCT_ID")},
inverseJoinColumns = {#JoinColumn(name = "CART_ID", referencedColumnName = "CART_ID")}
)
private List<CartEntity> carts = new ArrayList<>();
#ManyToOne
#JoinColumn(name = "GROUP_ID")
private Group groupId;
public Product(String name, String description, double price) {
this.name = name;
this.description = description;
this.price = price;
}
public Product(String name, String description, double price, Group groupId) {
this(name, description, price);
this.groupId = groupId;
}
public void addToCart(CartEntity cart) {
this.carts.add(cart);
cart.getProductsList().add(this);
}
public void addGroup(Group group) {
group.getProducts().add(this);
this.groupId = group;
}
#Getter
#NoArgsConstructor
#AllArgsConstructor
#Entity(name = "cart")
public class CartEntity {
#Id
#NotNull
#GeneratedValue
#Column(name = "CART_ID")
private Long id;
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "carts")
private List<Product> productsList = new ArrayList<>();
public void addProduct(Product product) {
productsList.add(product);
product.getCarts().add(this);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CartEntity that = (CartEntity) o;
return id.equals(that.id);
}
#Override
public int hashCode() {
return Objects.hash(id);
}
}
Now, when I have the following test:
public class ProductDaoTestSuite {
#Autowired
private ProductDao productDao;
#Autowired
private CartDaoStub cartDaoStub;
#Autowired
private GroupDao groupDao;
#Test
public void testDeleteProduct() {
// Given
Product product = new Product("test", "testProduct", 100.0);
Group group = new Group("group1");
CartEntity cart = new CartEntity();
product.addGroup(group);
cart.addProduct(product);
// When
groupDao.save(group);
productDao.save(product);
cartDaoStub.save(cart);
Long groupId = group.getId();
Long productId = product.getId();
Long cartId = cart.getId();
productDao.deleteById(productId);
// Then
Assert.assertTrue(cartDaoStub.findById(cartId).isPresent());
Assert.assertEquals(0, cartDaoStub.findById(cartId).get().getProductsList().size());
Assert.assertTrue(groupDao.findById(groupId).isPresent());
Assert.assertEquals(0, groupDao.findById(groupId).get().getProducts().size());
Following product deletion, I would expect association with it in group and cart to disappear (product to disappear from their List relationship fields). However, that is not happening at the moment. When I use Group/Cart Dao to pull group & cart from the DB after product deletion, they still have product in their Lists, while product when pulled from the DB is returned as null.
I have tried to add "orphanRemoval = true" value for #OneToMany adnotation, but it did not seem to work for Group entity.
What am I doing wrong?
I have started experimenting with adding all types of cascade (except for REMOVE) to #ManyToOne on Product class, but so far no luck.
For 1:N, yours should work just fine with minor adjustment.
The reason why it fails: Upon doing "groupDao.save(group);" this group is now in the persistence context and calling "groupDao.findById(groupId).get().getProducts().size()" would return the copy which is from the persistence context.
To solve this: simply add: entityManager.flush(); and entityManager.clear(); before the Assert
I would like to demonstrate it with this Integration Test
#Test
#Transactional
public void deleteProduct_groupShouldNowBeEmpty() {
ProductGroup group = groupRepository.findById("0001").orElseThrow(() -> new IllegalArgumentException("id not found"));
Assert.assertEquals(1, group.getProducts().size());
Product product = productRepository.findById("0001").orElseThrow(() -> new IllegalArgumentException("id not found"));
productRepository.delete(product);
entityManager.flush();
entityManager.clear();
Assert.assertEquals(0, productRepository.findAll().size());
Assert.assertEquals(0, groupRepository.findById("0001").get().getProducts().size());
}
If we are to remove the first 2 lines, then we won't need to flush and clear. Like this.
#Test
#Transactional
public void deleteProduct_groupShouldNowBeEmpty() {
Product product = productRepository.findById("0001").orElseThrow(() -> new IllegalArgumentException("id not found"));
productRepository.delete(product);
Assert.assertEquals(0, productRepository.findAll().size());
Assert.assertEquals(0, groupRepository.findById("0001").get().getProducts().size());
}
For N:M, since there would be another table where product is being referenced, then we would need to delete the records from that table first before deleting the product.
N:M is a bit tricky so if I can suggest domain changes, here how I'll do it. (The integration test is at the bottom.)
I'll add a separate entity: CartItem
which is associated to a Product and Cart
#Entity
public class CartItem {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
#ManyToOne
private Product product;
#ManyToOne
private Cart cart;
public String getId() {
return id;
}
// Required by JPA
protected CartItem() {}
}
And for the Product Entity: add a bidirectional relationship with CartItem
#Entity
public class Product {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
private String name;
private String description;
private BigDecimal price;
#ManyToOne
private ProductGroup group;
#OneToMany(mappedBy = "product")
private List<CartItem> cartItems;
public List<CartItem> getCartItems() {
return cartItems;
}
// Required by JPA
protected Product() {}
}
Then, retrieve the product (using Join Fetch to avoid N+1, since later will be looping through each cartItem)
public interface ProductRepository extends JpaRepository<Product, String> {
#Query("SELECT product FROM Product product JOIN FETCH product.cartItems")
Optional<Product> findProduct(String Id);
}
create another query inside CartItemRepository to delete cartItems in bulk by ids
public interface CartItemRepository extends JpaRepository<CartItem, String> {
#Modifying
#Query("DELETE FROM CartItem cartItem WHERE cartItem.id IN :ids")
void deleteByIds(#Param("ids") List<String> ids);
}
Lastly here's the integration test to wrap everthing up:
#Test
#Transactional
public void deleteProduct_associatedWithCart() {
Cart cart = cartRepository.findById("0001").get();
Assert.assertEquals(1, cart.getCartItems().size());
Product product = productRepository.findProduct("0001").orElseThrow(() -> new IllegalArgumentException("id not found"));
List<String> cartItemIds = product.getCartItems().stream()
.map(CartItem::getId)
.collect(Collectors.toList());
cartItemRepository.deleteByIds(cartItemIds);
productRepository.delete(product);
entityManager.flush();
entityManager.clear();
Assert.assertEquals(0, productRepository.findAll().size());
Assert.assertEquals(0, groupRepository.findById("0001").get().getProducts().size());
Assert.assertEquals(0, cartItemRepository.findAll().size());
Assert.assertEquals(0, cartRepository.findById("0001").get().getCartItems().size());
}
I've used DBUnit for this integration test so I think it would also be helpful to share the dataset.
<?xml version="1.0" encoding="UTF-8" ?>
<dataset>
<product_group id="0001" name="product group with 1 product"/>
<product id="0001" group_id="0001" />
<cart id="0001" />
<cart_item id="0001" product_id="0001" cart_id="0001" />
</dataset>
When you remove an entity, this state transition should be propagated from parent to child, not the other way around.
In this case, you need to move that functionally to the Group entity, something like this:
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Entity(name="Group")
public class Group {
#Id
#GeneratedValue
#NotNull
#Column(name = "GROUP_ID")
private Long id;
#Column(name="NAME")
private String name;
#OneToMany(
targetEntity = Product.class,
mappedBy = "groupId",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY, // Always prefer LAZY initialized Collections to EAGER ones
orphanRemoval = true
)
private List<Product> products = new ArrayList<>();
public Group(String name) {
this.name = name;
}
public void addProduct(Product product){
product.setGroupId(this);
this.products.add(product);
}
public void removeProduct(Product product){
product.setGroupId(null);
this.products.remove(product);
}
If you want to remove a Product, you only need to invoke the removeProduct method and save the parent entity:
Group group = new Group("group1");
Product product = new Product("test", "testProduct", 100.0);
group.addProduct(product);
groupDao.save(group);
On the other hand, we have the many-to-many relation between Product and CartEntity.
First, if you configure the entity CartEntity with Cascade.ALL as in your example:
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "carts")
private List<Product> productsList = new ArrayList<>();
It will have a probably undesired effect: if you remove the CartEntity, it will remove all the Products associated with the entity as well, even if other CartEntitys are still associated to them. Vlad Mihalcea explain it in great detail in this article.
To avoid that problem, the best option will be just define the relationship as follows:
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "carts")
private List<Product> productsList = new ArrayList<>();
This will give us a CartEntity like this:
#Getter
#NoArgsConstructor
#AllArgsConstructor
#Entity(name = "cart")
public class CartEntity {
#Id
#NotNull
#GeneratedValue
#Column(name = "CART_ID")
private Long id;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "carts")
private List<Product> productsList = new ArrayList<>();
public void addProduct(Product product) {
productsList.add(product);
product.getCarts().add(this);
}
public void removeProduct(Product product) {
productsList.remove(product);
product.getCarts().remove(this);
}
public void removeProducts() {
for(Product product : new ArrayList<>(products)) {
removeProduct(product);
}
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CartEntity that = (CartEntity) o;
return id.equals(that.id);
}
#Override
public int hashCode() {
return Objects.hash(id);
}
}
Please, note the inclusion of the removeProduct and removeProducts methods.
With this code, if you need to remove a CartEntity, just do the following:
cart.removeProducts();
cartDao.remove(cart);
And if you need to remove a Product from the CartEntity (will only remove the relation):
cart.removeProduct(product);
cartDao.save(cart);
If you need to propagate the Product remove to the CartEntity, I think that the best option will be create a business method that takes care of the whole process. Think in something like:
public void removeProduct(Product product){
Group group = product.getGroupId();
group.removeProduct(product);
final List<CartEntity> carts = product.getCarts();
if (carts != null) {
for(CartEntity cart : new ArrayList<>(carts)) {
cart.removeProduct(product);
cartDao.save(cart);
}
}
groupDao.save(group);
}
It will remove the association, you just need to do small adjustments.
1:N. When you remove Product, you don't have to do anything else in order to remove its association with Group, because the product itself holds the association (in DB column product.group_id). You just need to commit the transaction. And next time when you load a group from the DB it for sure will not contain this product.
N:M. There is no way to automatically remove the association because it is stored in a separate table and you don't have a separate entity for it. (YOU SHOULD NOT USE CascadeType.ALL for N:M relations). What you want to do is remove the association before you remove the product. Just add another helper method to Product.
public void removeFromCarts() {
carts.forEach(c -> c.getProducts().remove(this));
carts.clear();
}
So finally, in order to remove a product and all the associations with it. You will need to do the following:
product.removeFromCarts();
productDao.deleteById(productId); // not sure why you remove by id (not pass object)
*please note that you need to commit transaction and close the session. So you cannot rely on the test. In real app when you do what I described, it will work
**N:M is tricky. For instance, you should better use Set instead of List to avoid unexpected SQL under the hood. Also going down the road, I recommend you to consider splitting N:M into two N:1 and 1:M and have a dedicated Entity for a link table
Not sure I follow. Hibernate does not automatically maintain the inverse association for you. You can make it sensitive to changes on the owning side of the association, but that's as far as it goes.
As to why your test fails, cartDaoStub.findById(cartId) probably returns the same copy of the CartEntity that you already have loaded into the persistence context. Try calling entityManager.flush() followed by entityManager.clear() before making the assertion and the issue will probably go away.
I'm facing a difficulty in developing a server in Spring (+ Hibernate + JPA) for a project.
The structure of the server (the part of interest in this case) is composed of catalogs composed of products that can have some related feedbacks.
Here I share the 3 entities:
Catalog.java
#Entity
#Data
#Table(name = "catalog")
public class Catalog {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, unique = true)
private String name;
private String description;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "catalog_user_id", nullable = false)
private User user;
#ManyToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinTable(name = "catalog_product",
joinColumns = {#JoinColumn(name = "catalog_id")},
inverseJoinColumns = {#JoinColumn(name = "product_id")}
)
private List<Product> products;
public Catalog() {}
}
Product.java
#Entity
#Data
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, unique = true)
private String name;
private String description;
#Column(nullable = false, length = 1)
#MapKeyEnumerated(EnumType.ORDINAL)
private Category category;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "product_user_id", nullable = false)
private User user;
public Product() {}
}
Feedback.java
#Entity
#Data
#Table(name = "feedback")
public class Feedback {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "feedback_user_id", nullable = false)
private User user;
#Column(nullable = false, length = 1)
#MapKeyEnumerated(EnumType.ORDINAL)
private Rating rating;
private String text;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "product_id", nullable = false)
private Product product;
public Feedback() {}
}
The problem occurs when I try to delete some entities. What I want is:
when I delete a catalog also the catalog references in the "catalog_product" join table should be deleted (but the product linked with the catalog should not be deleted);
when I delete a product also the product references in the "catalog_product" join table and the feedbacks related to that product should be deleted;
when I delete a feedback nothing happens.
In the business layer I have this operations:
CatalogServiceImpl.java
#Service
public class CatalogServiceImpl implements CatalogService {
#Autowired
private CatalogDAO catalogDAO;
#Autowired
private ModelMapper mapper;
public CatalogDTO findById(Long id) {
Catalog catalog = catalogDAO.findById(id);
return mapper.map(catalog, CatalogDTO.class);
}
public CatalogDTO findByName(String name) {
Catalog catalog = catalogDAO.findByName(name);
return mapper.map(catalog, CatalogDTO.class);
}
public List<CatalogDTO> findByUserId(Long id) {
List<Catalog> catalogs = catalogDAO.findByUserId(id);
Type listCatalogsType = new TypeToken<List<CatalogDTO>>() {}.getType();
return mapper.map(catalogs, listCatalogsType);
}
public List<CatalogDTO> findAll() {
List<Catalog> catalogs = catalogDAO.findAll();
Type listCatalogsType = new TypeToken<List<CatalogDTO>>() {}.getType();
return mapper.map(catalogs, listCatalogsType);
}
public CatalogDTO createCatalog(CatalogDTO catalogDTO) {
Catalog catalog = mapper.map(catalogDTO, Catalog.class);
Catalog catalogFromDB = catalogDAO.save(catalog);
return mapper.map(catalogFromDB, CatalogDTO.class);
}
public CatalogDTO updateCatalog(CatalogDTO catalogDTO) {
Catalog catalog = mapper.map(catalogDTO, Catalog.class);
Catalog catalogFromDB;
if(catalogDAO.exists(catalog.getId())) {
catalogFromDB = catalogDAO.save(catalog);
} else {
catalogFromDB = null;
}
return mapper.map(catalogFromDB, CatalogDTO.class);
}
public void deleteCatalog(Long id) {
Catalog catalog = catalogDAO.findById(id);
if(catalog != null) {
catalogDAO.delete(catalog.getId());
}
}
}
ProductServiceImpl.java
#Service
public class ProductServiceImpl implements ProductService {
#Autowired
private ProductDAO productDAO;
#Autowired
private ModelMapper mapper;
public ProductDTO findById(Long id) {
Product product = productDAO.findById(id);
return mapper.map(product, ProductDTO.class);
}
public ProductDTO findByName(String name) {
Product product = productDAO.findByName(name);
return mapper.map(product, ProductDTO.class);
}
public ProductDTO findByCategory(Category category) {
Product product = productDAO.findByCategory(category);
return mapper.map(product, ProductDTO.class);
}
public List<ProductDTO> findByUserId(Long id) {
List<Product> products = productDAO.findByUserId(id);
Type listProductsType = new TypeToken<List<ProductDTO>>() {}.getType();
return mapper.map(products, listProductsType);
}
public List<ProductDTO> findAll() {
List<Product> products = productDAO.findAll();
Type listProductsType = new TypeToken<List<ProductDTO>>() {}.getType();
return mapper.map(products, listProductsType);
}
public ProductDTO createProduct(ProductDTO productDTO) {
Product product = mapper.map(productDTO, Product.class);
Product productFromDB = productDAO.save(product);
return mapper.map(productFromDB, ProductDTO.class);
}
public ProductDTO updateProduct(ProductDTO productDTO) {
Product product = mapper.map(productDTO, Product.class);
Product productFromDB;
if(productDAO.exists(product.getId())) {
System.out.println(product.toString());
productFromDB = productDAO.save(product);
} else {
productFromDB = null;
}
return mapper.map(productFromDB, ProductDTO.class);
}
public void deleteProduct(Long id) {
Product product = productDAO.findById(id);
if(product != null) {
productDAO.delete(product.getId());
}
}
}
Now, when I try performing the operations of deletion of catalog or product an error of constraint key fail is triggered. For example trying to delete a product which has a reference in the catalog_product join table:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`e01`.`catalog_product`, CONSTRAINT `FKdx5j7bcx77t7h0hjw6tvoxmp1` FOREIGN KEY (`product_id`) REFERENCES `product` (`id`))
I don't understand if there's a way to set the relations between entities to make what I want in an automatic way with Spring, or if I have to remove records with reference manually before the deletion of the catalog/product.
Thanks a lot in advance to everyone!
Luca
I have below entities and #ManyToMany mapping between the two.
#Entity
#Table(name = "user")
public class User implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 5340562707217344212L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
private String userName;
private String password;
private String firstName;
private String lastName;
private String emailId;
private Date createdDate;
private Byte status;
private Date lastModifiedDate;
#ManyToMany
#JoinTable(name = "user_products_mapper",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "product_id")
)
private List<Products> products = new ArrayList<Products>();
public void addProducts(Products product){
this.products.add(product);
}
#Entity
#Table(name="products")
public class Products implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1895580713896126954L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long productId;
private String productName;
private String description;
private double minBalance;
public Long getProductId() {
return this.productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public String getProductName() {
return this.productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
My question is :
1)I already have these entities persisted. (User and Products)
2)User and Products are already exists in databasae and relation between them is #ManyToMany
3)I want to insert records ONLY IN JoinTable (user_products_mapper) since the rows are already present for User and Products.
4)One way to achieve this is to use UNIDIRECTIONAL mapping as I have above and just call
User.addProducts
5) Before doing this User and Products entities will be fetched like below :
User user = this.userDao.findOne(userId);
if(user == null){
//throw exception
}
Products product = this.productDao.findOne(productId);
6)Is there any way of doing this ?
When using Hibernate/JPA you do not control the actual queries being run, but you are not supposed to either.
What you would do to connect a user and a product would be something like:
#Transactional
public void addProductToUser(final Long userId, final Long productId) {
final User user = em.find(User.class, userId);
final Product product = em.find(Product.class, productId);
user.addProduct(product);
}
This will result in more queries than just inserting a row (since you will fetch both Product and User), but what you gain is simple and plain Java code. The cost of the extra queries in most cases is well within what is acceptable.
I am learning Hibernate, and I have a question about basic HQL join syntax. I am following this tutorial. Say I have a Product and Category entity,
#Entity
#Table(name = "CATEGORY")
public class Category {
private long id;
private String name;
private Set<Product> products;
public Category() {
}
public Category(String name) {
this.name = name;
}
#Id
#Column(name = "CATEGORY_ID")
#GeneratedValue
public long getId() {
return id;
}
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
public Set<Product> getProducts() {
return products;
}
// other getters and setters
}
#Entity
#Table(name = "PRODUCT")
public class Product {
private long id;
private String name;
private String description;
private float price;
private Category category;
public Product() {
}
public Product(String name, String description, float price,
Category category) {
this.name = name;
this.description = description;
this.price = price;
this.category = category;
}
#Id
#Column(name = "PRODUCT_ID")
#GeneratedValue
public long getId() {
return id;
}
#ManyToOne
#JoinColumn(name = "CATEGORY_ID")
public Category getCategory() {
return category;
}
// other getters and setters
}
So I have to join category and Product, I will something like this in sql
select * from Category A inner join Product B on A.id=B.category_id,
In HQL, it seems we drop the "on" condition, the HQL for the above query is
String hql = "from Product p inner join p.category";
Query query = session.createQuery(hql);
Why is on not required in HQL?
If you have an association (for an example #ManyToOne), you don't need on, Hibernate will add it to the SQL.
It was a problem prior to Hibernate 5.1, If you don't have an association. From Hibernate 5.1 you can use ad hoc joins:
How to join unrelated entities with JPA and Hibernate
Apart that, HQL also defines a with clause to qualify the join conditions:
Hibernate docs: Explicit joins
I have been looking the forums and everywhere for a unidirectional manyToMany query. I find many examples but i can't really adjust them to my needs :(
I have 2 entity classes (Anime & User)
Anime {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "anime_id")
private Integer id;
//Other fields etc.
}
User {
#Id
#ValidUsername
#Column(name = "user_id")
private String username;
#ManyToMany()
#JoinTable(name = "users_animes",
joinColumns = #JoinColumn(name = "anime_id", referencedColumnName = "anime_id"),
inverseJoinColumns = #JoinColumn(name = "user_id", referencedColumnName = "user_id"))
private final List<Anime> animes = new ArrayList<>();
}
Anime simply holds the data from the anime.
User holds the username etc of the user and a list of anime that he subscribed to.
Now i'm trying to find a query that will let me get all the animes in that list.
It's mapped in a table as "users_animes".
Would be a huge help since i'm fairly new to JPQL.
Thanks!
Here's a simple example. Let's assume that we have Country Entity, which can have multiple Citizens:
#Entity
#Table (name = "countries")
public class Country implements Serializable {
#Id
#Column (name = "coun_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column (name = "country_name")
private String countryName;
#ManyToMany (cascade = CascadeType.ALL)
#JoinTable (
name = "citizen_country",
joinColumns = #JoinColumn (name = "country_id", referencedColumnName = "coun_id"),
inverseJoinColumns = #JoinColumn (name = "citizen_id", referencedColumnName = "cit_id")
)
private List<Citizen> citizens;
public Country() {}
public Country(String countryName) {
this.countryName = countryName;
}
//getters and setters
}
And Citizen, which can belong to multiple Countries:
#Entity
#Table (name = "citizens")
public class Citizen implements Serializable {
#Id
#Column (name = "cit_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column (name = "country_name")
private String citizenName;
public Citizen() {}
public Citizen(String citizenName) {
this.citizenName = citizenName;
}
public void setId(Long id) { this.id = id; }
public Long getId() { return id; }
public void setCitizenName(String citizenName) { this.citizenName = citizenName; }
public String getCitizenName() { return citizenName; }
//getters and setters
}
It's unidirectional, just as you wanted it to be. Therefore, Citizen Entity is unaware of Country, so you can't get directly information to which Country a certain Citizen belongs. However, you can retrieve info about which Citizens belong to a certain Country.
Going further, you can populate your tables:
Citizen citizen1 = new Citizen("Smith");
Citizen citizen2 = new Citizen("Kowalski");
Citizen citizen3 = new Citizen("Muller");
dataAccess.saveCitizen(citizen1);
dataAccess.saveCitizen(citizen2);
dataAccess.saveCitizen(citizen3);
// now let's fetch them from DB, along with their other properties (only id in this case)
citizen1 = dataAccess.getCitizenByName("Smith");
citizen2 = dataAccess.getCitizenByName("Kowalski");
citizen3 = dataAccess.getCitizenByName("Muller");
Country country1 = new Country("Foo");
Country country2 = new Country("Bar");
// create lists to hold citizens for each country
List<Citizen> citizenList1 = new ArrayList();
List<Citizen> citizenList2 = new ArrayList();
// add elements to the lists
citizenList1.add(citizen1);
citizenList1.add(citizen2);
citizenList2.add(citizen2);
citizenList2.add(citizen3);
//assign lists of citizens to each country
country1.setCitizens(citizenList1);
country2.setCitizens(citizenList2);
//save data in DB
dataAccess.saveCountry(country1);
dataAccess.saveCountry(country2);
//fetch list of all persisted countries (associated Citizens will come along)
countries = dataAccess.getAllCountries();
And finally:
#Stateless
public class DataAccess {
#PersistenceContext
EntityManager em;
public void saveCountry(Country country) {
em.persist(country);
}
public void saveCitizen(Citizen citizen) {
em.persist(citizen);
}
public Citizen getCitizenByName(String name) {
Query query = em.createQuery("SELECT c FROM Citizen c WHERE c.citizenName = :name");
query.setParameter("name", name);
return (Citizen) query.getSingleResult();
}
public List<Country> getAllCountries() {
Query query = em.createQuery("SELECT c FROM Country c");
return (List<Country>) query.getResultList();
}
}