How to avoid extra table while joining the two columns in JPA? - java

I have two classes A & B with as :
This is the class A
class A{
private int id;
private String name ;
private String emailId;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy="aList")
List<B> bList;
// getter and setters
}
This is the class B
class B{
private int id;
private int AId ;
private String location;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
List<A> aList;
// getter and setters
}
And this is the Dao Class method with which I am trying to get the joined Data.
public List<A> getA() {
Session session = entityManagerFactory.unwrap(SessionFactory.class).openSession();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<A> criteria = builder.createQuery(A.class);
Root<A> root = criteria.from(A.class);
Join<A, B> joinList = root.join("bList", JoinType.LEFT);
try{
Predicate filters = builder.and(builder.equal(joinList.get("AId"),root.get("id")),
builder.equal(root.get("name"), "xxx"));
criteria.where(filters);
List<A> aList = (List<A>)session.createQuery(criteria).getResultList();
return aList;
}catch(Exception e){
e.printStackTrace();
}
return null;
}
But every time it is creating another table and I don't want the extra table to be created rather I want to apply join like we do in mysql i.e without creating any extra table. How it can be done ??
Thanks

If I correctly understood you. JOIN operator is not creating new tables on its own. It is rather the way you described your entity. You have used Many to many relation which created another table. This table is needed to satisfy 3 Normal Form of database.
Solution
Accept existence of another table, until you like to keep consistency of your DB, and prevent data leaks
Redefine your entity so it has Many to one or One to many relation

Related

Spring JPA Join Efficiency - Create a query for each iteration

I have a simple 2 JPA entities which I have a Join Between them:
Primary entity Country:
public class Country implements Serializable {
#Id
#Column(name = "MCC")
private String mcc;
......
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "mcc", referencedColumnName = "mcc")
private List<CountryInfo> countryInfo;
Joint entity CountryInfo:
public class CountryInfo implements Serializable {
#Id
#Column(name = "id")
private Long id;
#Column(name = "mcc")
private String mcc;
#Column(name = "continent")
private String continent;
When I've turned on my configuration to dump the queries being executed, I've noticed that for each Country found, another call is done on the CountryInfo entity with the mcc specified..
This is obviously slow since rather than creating 1 call with a JOIN, it is executing N + 1 queries (where N = count of Country).
I've already seen this tutorial https://zeroturnaround.com/rebellabs/how-to-use-jpa-correctly-to-avoid-complaints-of-a-slow-application/ and changed accordingly but it is still calling N + 1 queries..
Is there a way to overcome this?
EDIT
In order to get the data I have a Repository:
#RepositoryRestResource(exported = false)
public interface CountryRepository extends JpaRepository<E212MCC, Long>,
JpaSpecificationExecutor<E212MCC> {
}
And then call with some specifications:
List<E212MCC> countries = this.countryRepository.findAll(specifications);
Since you are using Specifications you could try with specification that performs fetch join operation (I am assuming that you are using JPA meta model):
private Specification<Country> joinContryInfo() {
return (root, query, cb) -> {
root.fetch(Country_.countryInfo);
// here you can fetch more entities if you need...
return null;
};
}
And then, just add it to your specification object:
Specifications.where(joinCountryInfo())
If you are not using meta model then just replace Country_.countryInfo with "countryInfo" string.
If you are using CountryInfo fields for searching, you can omit joinContryInfo() specification and prepare join and search query in one specification:
private Specification<Country> continentEqual(String param) {
return (root, query, cb) -> {
Join<Country,CountryInfo> join = (Join) root.fetch(Country_.countryInfo);
return cb.equal(join.get(CountryInfo_.continent), addWildCards(param));;
};
}

JPA nativeQuery returns cached resultList

I have following classes:
Company.class:
public class Company {
#JoinTable(name = "company_employee", joinColumns = #JoinColumn(name = "company_id") , inverseJoinColumns = #JoinColumn(name = "employee_id") )
#ManyToMany(fetch = FetchType.LAZY)
private Set<Employee> employees;
#Column(name = "score")
private BigDecimal score;
}
and Employee.class
public class Employee {
#ManyToMany(fetch = FetchType.EAGER, mappedBy="employees")
private Set<Company> companies;
}
The Score column of Company is always null in the db and never updated via dao, because there is other table containing score for each unique pair Company-Employee.
I need the value of Score, only for the case when I fetch Employee by id, so this case all Company instances in the Set should contain score, thus I will get Employee-Company score pairs where employee is fetched Employee.
I have following code to achieve that:
public Employee get(Long id) {
Employee emp = (Employee) dao.find(id);
List<Company> compList = compnanyService.getByEmpId(id);
Set<Company> compSet = new HashSet<Company>(compList);
emp.setCompanies(compSet);
return emp;
}
And Company Dao contains method:
public List<Company> getByEmpId(Long id) {
final Query query = this.entityManager.createNativeQuery("select company.comp_id, ...some other fields, score.score from company join score on company.company_id=score.company_id where score.employee_id=:employee_id",
Company.class);
query.setParameter("employee_id", id);
List<Company> comps = query.getResultList();
return comps;
}
The problem is that getByEmpId(id) gives a ResultList where company.score is null though executed in the db it is not null.
I suspected that there is some caching intervening, so I tried to remove some columns from the native query, and it should have invoked an exception with "no column found" (or alike) message while mapping, but this method still gives List<Company> with all fields on their places though Hibernate prints out my native query in the console with all changes I make.
What am I doing wrong here and how to achieve what I need? Thank you.
It might be associated with first level cache, which can be out of sync when using native SQL queries. From here:
If you bypass JPA and execute DML directly on the database, either
through native SQL queries, JDBC, or JPQL UPDATE or DELETE queries,
then the database can be out of synch with the 1st level cache. If you
had accessed objects before executing the DML, they will have the old
state and not include the changes. Depending on what you are doing
this may be ok, otherwise you may want to refresh the affected objects
from the database.
So you can try using refresh method from EntityManager.
So I ended up doing that:
Created view in db from the query:
CREATE VIEW companyscore AS select company.comp_id, score.emp_id ...some other fields, score.score from company join score on company.comp_id=score.comp_id;
Created corresponding entity CompanyScore with composite primary id as comp_id and emp_id and created view as table.
Changed Employee entity to:
public class Employee {
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "emp_id")
private Set<CompanyScore> companies;
}
This way I not only have score field always consistent, but I can choose set of fields to show as the whole Company class is quite extensive and I don't need all the fields for this particular case.

JPA foreign key - insert new record if necessary?

Is it possible to create some combination of annotations, that provide following:
Have 2 tables (one-2-many relationship)
Is it possible on JPA level without programming, just create object of "one" class and if there is id set that just make it reference in "many" table and in case that id is not set, create new record in "one" table and make reference to that id in "many" table
No. You will need to create all the objects by your self.
What you can do is to use cascade to do a automatic persist if you want to.
It would be something like:
a.setB(b);
b.getAList().add(a);
entityManager.persist(a);
And in your classes you would map like:
public class A {
// other methods and ID
#ManyToOne
#JoinColumn(name = "b_id")
private B b;
}
public class B {
// other methods and ID
#OneToMany(mappedBy = "b", cascade = CascadeType.PERSIST)
private List<A> aList;
}

Pass a object as query parameter and exclude the ID column

I have the following:
#Entity
#NamedQuery(name = "listCarsBySecurity", query = "SELECT c FROM Car c WHERE c.security = :security"
public class Car {
#Id
#GeneratedValue
private Long id;
#NotNull()
#Column(nullable = false)
private String make;
#NotNull()
#Column(nullable = false)
private String model;
// Some more fields
#NotNull()
#OneToOne (fetch = FetchType.LAZY, orphanRemoval=true)
private Security security = new Security();
// Some getters and setters
As you can see, the Car class has a "Security" object which is LAZY fetched. The security class looks like:
#Entity
public class Security {
#Id #GeneratedValue
private Long id;
// Security equipment. Add in alphanumerical order
private boolean abs;
private boolean airbag;
private boolean antispin;
// Some getters and setters
as you can see, the named query list try to list all cars which has a security entity equal to a provided security object.
The persistence method looks like:
#Stateless
public class CarEJB {
#PersistenceContext(unitName = "carcmsPU")
private EntityManager em;
public List<Car> listCarsBySecurity(Security security) {
TypedQuery<Car> query = em.createNamedQuery("listCarsBySecurity", Car.class);
query.setParameter("security", security);
return query.getResultList();
}
And a junit test looks like:
#Test
public void searchCar() throws Exception {
// Looks up the EJBs
carEJB = (CarEJB) ctx.lookup("java:global/classes/CarEJB");
// Create a new Ferrari with security = ABS brakes and Airbag
Car car = new Car();
car.setMake("Ferrari");
car.setModel("Some model");
car.setSubModel("Some sub model");
car.setEngine("Some engine");
car.setYear(1999);
car.getFuel().setGasoline(true);
car.setGearbox(Gearbox.AUTOMATIC);
car.setKilometres(323);
car.setDescription("This is a description");
Security security = new Security();
security.setAbs(true);
security.setAirbag(true);
car.setSecurity(security);
carEJB.createCar(car); // Persist
// Create a new security object and match it to the former one
Security securityObject = new Security();
securityObject.setAbs(true);
securityObject.setAirbag(true);
List<Car> carList = carEJB.listCarsBySecurity(securityObject);
assertTrue("Should contain at least 1 car with ABS and Airbag", carList.size() > 0 );
for (Car carTemporary : carList) {
System.out.println(carTemporary.toString());
}
}
The thing is that the list does not contain any cars at all. And I think I know why; the named query does try to match the security_id with NULL (since I have not define it).
My question is: How can I perform a query by passing a object as a query parameter with no ID and by not specify all fields which shall be compared inside that object? (or how exclude the ID from a search)?
Best regards
You can define a named query using OR and passing each one of the object's attributes. You can also use Criteria API to build a query based on the fields you want to query about. Since you already have a named query I'll leave that one to you.
If you decide to go that way (tough field by field comparation is kind of insane if your entity has way too many attributes). Using criteria you can do something like this:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Car> query = builder.createQuery(Car.class);
Root<Car> queryRoot = query.from(Car.class);
query.select(queryRoot);
Path<String> pathToYourField = root.get(yourField); //yourField is a variable containing the field.
//You can store all the variables in a list, iterate
//over them and do this for each one.
query.where(builder.and(builder.equal(pathToYourField, "particularValue"))); //You compare the path against a value.
//Rest of the fields / paths
TypedQuery<Car> typedQuery = entityManager.createQuery(query);
List<Car> cars = typedQuery.getResultList();
EDIT: About performance, check this links:
JPA Criteria vs NamedQueries
Another answer regarding Criteria vs HQL
Criteria overhead discussion

self-reference field mapping in JPA

Lets say we have User entity class. User can be friends with other users. How can i map this self-reference collection field without creating a new entity called Connection or creating multiple entries in the database?
#Entity
public class User {
...
#ManyToMany
private Collection<User> friends;
...
}
USER_ID-FRIEND_ID
1 - 2
2 - 1 (duplicate... I don't need it)
Following is snapshot from my code for ElementEntity:
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private List<ElementEntity> children;
#JoinColumn(name = "ParentId", referencedColumnName = "ElementId")
#ManyToOne(fetch = FetchType.LAZY)
private ElementEntity parent;
Where on database there are fields:
ElementId - primary key;
ParentId relation with parent
You can't - you need both records in the database.
Actually, for friendship relations, I'd say that a graph database like neo4j is the proper thing to use. There you have the two users and simply add an edge "friends".
At least you will need a relational table.
So you have a USER table and a FRIENDS:
user_id friend_id
1 2
But #Bozho answer is way better than mine (neo4j).
Well, in fact you can.
You can use annotations like #PreUpdate, #PrePersists, #PostUpdate and so to convert manually the elements of a collection. This way your entity can render then them way you want while in database you only store a raw text.
A more pausible alternative will be to use #Convert annotation, available since jpa 2.1 (#UserType in hibernate). It tells jpa to convert the field into another type everytime it read/save in database.
For it you should use #Convert anotation, specifying and AttributeConverter object.
For example
public class Parent {
#Id
private Integer id;
#Convert(converter = FriendConverter.class)
private Set<Parent>friends;
}
And converter class like the following:
#Component
public class FriendConverter implements AttributeConverter<List, String>{
#Autowired
private SomeRepository someRepository;
#Override
public String convertToDatabaseColumn(List attribute) {
StringBuilder sb = new StringBuilder();
for (Object object : attribute) {
Parent parent = (parent) object;
sb.append(parent.getId()).append(".");
}
return sb.toString();
}
#Override
public List convertToEntityAttribute(String dbData) {
String[] split = dbData.split(".");
List<Parent>friends = new ArrayList<>();
for (String string : split) {
Parent parent = someRepository.findById(Integer.valueOf(string));
friends.add(accion);
}
return friends;
}
}
It is a dummy implementation but it gives you the idea.
As a personal comment, I do recommend to map the relationship as it should. In the future it will avoid you problems. AttributeConverter comes in handy when working with enums

Categories