one side set in many-to-many relation - jpa

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 :)

Related

Many To Many Relationship JPA with Entity

I have an issue trying to generate multiple relationship in JPA with three Entities.
Order
Product
Modifier
I have an Entity to handle the relationship many to many.
OrderProducts (order_id and product_id)
Contains the relationship of one order can have multiple products
OrderDetails (order_products_id and modifier_id)
Contains the id of the previous relationship Order-Products and the Id of the modifier which is a set of multiple values that can affect the price of the product.
Not quite sure how to handle this kind of relationship in JPA as I'm new to it.
You need a join entity with a composite key. You will need to research it further.
Your entities:
#Entity
#Table(name = "ordertable")
#Data
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "order")
#EqualsAndHashCode.Exclude
private Set<OrderProductModifier> products;
}
#Entity
#Table(name = "product")
#Data
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#EqualsAndHashCode.Exclude
private BigDecimal unitPrice;
}
#Entity
#Table(name = "modifier")
#Data
public class Modifier {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#EqualsAndHashCode.Exclude
private BigDecimal modifier;
}
And the entity that ties it all together will need to have the foreign keys for each of the above entities, as you have noted.
#Entity
#Table(name = "orderproductmodifier")
#Data
public class OrderProductModifier {
#EmbeddedId
private OrderProductModifierId id;
#MapsId("orderId")
#ManyToOne
#EqualsAndHashCode.Exclude
#ToString.Exclude
private Order order;
#MapsId("productId")
#ManyToOne
#EqualsAndHashCode.Exclude
private Product product;
#MapsId("modifierId")
#ManyToOne
#EqualsAndHashCode.Exclude
private Modifier modifier;
}
#SuppressWarnings("serial")
#Embeddable
#Data
public class OrderProductModifierId implements Serializable {
private Long orderId;
private Long productId;
private Long modifierId;
}
This is pretty simple to use:
private void run() {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("UsersDB");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Product product = new Product();
product.setUnitPrice(BigDecimal.TEN);
em.persist(product);
Modifier modifier = new Modifier();
modifier.setModifier(new BigDecimal(".90"));
em.persist(modifier);
Order order = new Order();
em.persist(order);
OrderProductModifier opm = new OrderProductModifier();
opm.setId(new OrderProductModifierId());
opm.setOrder(order);
opm.setProduct(product);
opm.setModifier(modifier);
em.persist(opm);
em.getTransaction().commit();
em.clear();
Order o = em.createQuery("select o from Order o join fetch o.products where o.id = 1", Order.class).getSingleResult();
System.out.println("Order for " + o.getProducts());
System.out.println("Order cost " + o.getProducts().stream().map(p->p.getProduct().getUnitPrice().multiply(p.getModifier().getModifier()).doubleValue()).collect(Collectors.summingDouble(Double::doubleValue)));
}
The above query could be better, but that will give you something to work on.

spring data error when trying to sort by a field of joined entity inside a crudrepository

I am using springboot and springdata with Mysql.
I have 2 entities, Customer & Order:
#Entity
#Table(name = "customers")
public class Customer {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="id", nullable = false)
protected long id;
#Column(name = "name")
private String name;
}
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="id", nullable = false)
protected long id;
#Column(name="customer_id")
private long customerId;
}
I also have a repository:
#Repository
public interface OrdersRepository extends JpaRepository<Order, Long> {
#Query("select o from Order o, Customer c where o.customerId = c.id")
Page<Order> searchOrders(final Pageable pageable);
}
The method has some more arguments for searching, but the problem is when I send a PageRequest object with sort that is a property of Customer.
e.g.
Sort sort = new Sort(Sort.Direction.ASC, "c.name");
ordersRepository.search(new PageRequest(x, y, sort));
However, sorting by a field of Order works well:
Sort sort = new Sort(Sort.Direction.ASC, "id");
ordersRepository.search(new PageRequest(x, y, sort));
The error I get is that c is not a property of Order (but since the query is a join of the entities I would expect it to work).
Caused by: org.hibernate.QueryException: could not resolve property c of Order
Do you have any idea how I can sort by a field of the joined entity?
Thank you
In JPA , the thing that you sort with must be something that is returned in the select statement, you can't sort with a property that is not returned
You got the error because the relationship is not modeled properly. In your case it is a ManyToOne relation. I can recomend the wikibooks to read further.
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="id", nullable = false)
protected long id;
#ManyToOne
#JoinColumn(name="customer_id", referencedColumnName = "id")
private Customer customer;
}
The query is not needed anymore because the customer will be fetched.
#Repository
public interface OrdersRepository extends PagingAndSortingRepository<Order, Long> {
}
Now you can use nested properties.
Sort sort = new Sort(Sort.Direction.ASC, "customer.name");
ordersRepository.findAll(new PageRequest(x, y, sort));

JPArepository method with ManyToOne not return related data

I have two entities using a OneToMany and ManyToOne relationship:
#Entity
#Table(name = TableName.PERSON)
public class Person {
#Id
#Column(name = FieldName.DB_KEY)
public String dbKey;
#Column(name = FieldName.ENTITY_ID)
public String entityId;
#Column(name = FieldName.SORT_KEY)
public String sortKey;
#JoinColumn(name = FieldName.ENTITY_ID, referencedColumnName =
FieldName.ENTITY_ID, insertable = false, updatable = false)
#ManyToOne(fetch = FetchType.EAGER)
#JoinFetch(value = JoinFetchType.INNER)
public WLEntity entity;
}
#Entity
#Table(name = TableName.WL_ENTITY)
public class WLEntity {
#Id
#Column(name = FieldName.ENTITY_ID)
public String entityId;
#Column(name = FieldName.HAS_ADDRESS)
public boolean hasAddress;
#OneToMany(mappedBy = "entity", targetEntity = PersonIndex.class, cascade = CascadeType.ALL)
public List<Person> persons;
}
And a JPA-Repository defining one findBy... Method:
#Repository
public interface PersonRepository extends JpaRepository<Person, String> {
List<Person> findBySortKeyStartingWith(String sortKey);
}
If I call this method I can see in the console:
SELECT t1.DB_KEY, t1.ENTITY_ID, t1.SORT_KEY, t0.ENTITY_ID, t0.HAS_ADDRESS FROM WL_ENTITY t0, PERSON t1 WHERE (t1.SORT_KEY LIKE ? AND (t0.ENTITY_ID = t1.ENTITY_ID))
So the join I want is correct executed, but in the returned data the entity field is still null but all other fields are filled:
List<Person> persons = personRepository.findBySortKeyStartingWith("Sort");
Person person = persons.get(0);
person.entity == null but person.entityId is correctly filled.
So what I have to do, to get person.entity filled?
I use spring boot with eclipselink jpa 2.6.4

using #Embedabble with a foreign key and manyToMany relation

I wrote an example for the code i am trying to implement, i get an error with Constraint "Student_Teacher_FK" already exists.
the #embiddable class has a foreign key that is created twice with current code.
#Entity
public class Teacher {
#Id
#GeneratedValue
private Long id;
#Column(name = "Name")
private String name;
}
#Entity
public class Student{
#Id
#GeneratedValue
private Long id;
#Column(name = "Name")
private String name;
}
#Embeddable
public class StudentList implements Serializable {
#ManyToMany
#JoinTable(name = "Student_Teacher",
joinColumns =
#JoinColumn(name = "Student_ID", referencedColumnName = "ID"),
inverseJoinColumns =
#JoinColumn(name = "Teacher_ID", referencedColumnName = "ID")
)
#ForeignKey(name = "Student_Teacher_FK", inverseName = "Teacher_Student_FK")
public List<Student> studentList = new ArrayList<Student>();
}
#Entity
public class HistoryTeacher extends Teacher {
#Embedded
#NotNull
private StudentList StudentList = new StudentList ();
}
#Entity
public class LangTeacher extends Teacher {
#Embedded
#NotNull
private StudentList StudentList = new StudentList ();
}
#Entity
public class RetiredTeacher extends Teacher {
// has no students
}
#embeddable : Defines a class whose instances are stored as an intrinsic part of an owning entity and share the identity of the entity (http://docs.oracle.com/javaee/6/api/javax/persistence/Embeddable.html)
As you are declaring it in 2 different entity, jpa will create associated association table (student-teacher) 2 times with associated fk, which is explicitely named, and so created 2 times too with the same name. Here is your error.
I don't think using #embeddable is appropriated for what you're intending to do. A student has is own existence and is not part of teacher itself (not an uml composition / black diamond) so it's not an embeddable entity. Student list should be held by teacher entity using a simple manyToMany association.

JPA query many to one association

I want to build the following pseudo query
Select a From APDU a where a.group.id= :id
group is a field in APDU class of the type APDUGroup.class.
I just want to get a list of APDUs based on APDUGroup's id.
How do i do that using a standard JPA query?
UPDATE
Yes, I have tried the above query and tried other variations for hours before posting in S/O. Here is the generated SQL for the query above:
SELECT t1.ID, t1.status, t1.type, t1.modified, t1.response, t1.expectedSize, t1.created, t1.description, t1.sequence, t1.name, t1.command, t1.recurring, t1.auth, t1.createdBy, t1.APDUGroup, t1.modifiedBy FROM APDUGroup t0, APDU t1 WHERE ((t0.ID = ?) AND (t0.ID = t1.APDUGroup))
The query looks okay but nothing get selected from my table.
There are at least 100 APDUs with APDUGroup = 1 in my test database.
I'm using eclipselink as the JPA provider.
Given the following Entities:
#Entity
public class APDU implements Serializable {
#Id
#GeneratedValue
private Long id;
#ManyToOne
private APDUGroup group;
//...
}
#Entity
public class APDUGroup implements Serializable {
#Id
#GeneratedValue
private Long id;
//...
}
The following query will return a list of APDUs for a given APDUGroup id:
select a from APDU a where a.group.id = :id
Oh, wait, that's your query :)
Entity 1:
#Entity
#Getter
#Setter
#Table(name = "invoices")
public class Invoice implements Serializable {
#Id
#GeneratedValue
#Column(name = "invoice_id", updatable = false, nullable = false)
private Long invoiceId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "person_id", referencedColumnName = "person_id", insertable = false, updatable = false, nullable = false)
private Person person;
//...
}
Entity 2:
#Entity
#Getter
#Setter
#Table(name = "people")
public class Person implements Serializable {
#Id
#GeneratedValue
#Column(name = "person_id", updatable = false, nullable = false)
private Long personId;
//...
}
Finally, Your Data Access Object (JPA Repository)
#Repository
public interface InvoiceRepository extends JpaRepository<Invoice, Long> {
#Query(value="SELECT x FROM Invoice x WHERE x.person.personId = :myPersonId")
List<Invoice> findInvoiceByPersonId (long myPersonId);
}
I hope this example has been helpful :)