I have the following entity, and i want to order the list of typeValuesList by the field TypeValues -> name (string) in descending order. I want to do this conditionally only when the description is equals to "1" for example. I tried with the #OrderBy annotation but that does not seem to work conditionally, what are my options in this case, can i go with a query?
#Entity
#Table(name="DEVICE")
public class Device extends AbstractEntity {
#Column(name = "DESCRIPTION", length = 300)
#Size(max = 150)
private String description;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name="TYPE_ID", nullable = false)
private List<TypeValues> typeValuesList = new ArrayList<>();
}
#Entity
#Table(name="TYPE_VALUES")
public class TypeValues extends AbstractEntity{
#Column(name = "NAME", length = 50)
#Size(max = 25)
private String name;
}
Since you want to do it conditionally the best solution is to sort your collection as needed upon retrieval
in the calling method after retrieving the Device:
Collections.sort(device.getTypeValuesList(), Comparator.comparing(TypeValues::getName));
Related
I'm learning how Spring framework works and as an example I'm trying to save cities and countries which users can log using the API endpoints. However, I can't figure out how to prevent duplicate entries.
For example I'm adding 2 cities in a country using the endpoint (photo below) but in the Country table I get duplicate values. How can I prevent duplicate values ? Thanks in advance.
#Getter
#Setter
#Entity
#Table(name = "COUNTRY")
public class CntCountry {
#Id
#SequenceGenerator(name = "CntCountry", sequenceName = "CNT_COUNTRY_ID_SEQ")
#GeneratedValue(generator = "CntCountry")
private Long id;
#Column(name = "COUNTRY_NAME", length = 30, nullable = false)
private String countryName;
#Column(name = "COUNTRY_CODE", length = 30, nullable = false)
private String countryCode;
}
#Getter
#Setter
#Table(name = "CITY")
#Entity
public class CtyCity {
#Id
#SequenceGenerator(name = "CtyCity", sequenceName = "CTY_CITY_ID_SEQ")
#GeneratedValue(generator = "CtyCity")
private Long id;
#Column(name = "CITY_NAME", length = 30, nullable = false)
private String cityName;
#Column(name = "PLATE_NUMBER", length = 30, nullable = false)
private Long plateNumber;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "FK_COUNTRY")
private CntCountry country;
}
EDIT:
#PostMapping("/city")
public ResponseEntity<CtyCityDto> save(#RequestBody CtyCitySaveRequestDto ctyCitySaveRequestDto){
CtyCityDto ctyCityDto = ctyCityService.save(ctyCitySaveRequestDto);
return ResponseEntity.ok(ctyCityDto);
}
#Service
#AllArgsConstructor
public class CtyCityService {
private CtyCityDao ctyCityDao;
public CtyCityDto save(CtyCitySaveRequestDto ctyCitySaveRequestDto){
CtyCity ctyCity = CtyCityMapper.INSTANCE.convertToCtyCity(ctyCitySaveRequestDto);
ctyCity = ctyCityDao.save(ctyCity);
CtyCityDto ctyCityDto = CtyCityMapper.INSTANCE.convertToCtyCityDto(ctyCity);
return ctyCityDto;
}
}
public interface CtyCityDao extends JpaRepository<CtyCity,Long> {
}
#Data
public class CtyCityDto {
private Long id;
private String cityName;
private Long plateNumber;
private CntCountry country;
}
I'm not really following your naming conventions, and I think your DTO classes are just complicating things for you at this point... But in general terms, because the entities you're sending have no id value associated with them, JPA assumes they are different objects and adds them to the database with new id's because it hasn't been told anywhere that similar items might in fact be the same object, it needs to be told.
I can think of 2 ways to prevent entity duplication in your database.
1. The easiest way would be to set your Country and City names (or other attributes) to be "unique", you can do this in your entity classes simply by adding unique = true to the column data on the item you wish to be unique.
//In Country.java
#Column(name = "COUNTRY_NAME", length = 30, nullable = false, unique = true)
private String countryName;
//In City.java
#Column(name = "CITY_NAME", length = 30, nullable = false, unique = true)
private String cityName;
Although, you will then need to handle exceptions thrown if a duplicate is provided, in Spring Boot the best way to handle this is with a #ControllerAdvice but that's another subject.
2. Check if the entity exists by name or some other value. A common approach might be something like the following:
//In your service
public Object saveCountry(Country country){
Country existingCountry = countryRepository.findByName(country.getName()).orElse(null);
if(existingCountry == null){
//Country does not already exist so save the new Country
return countryRepository.save(country);
}
//The Country was found by name, so don't add a duplicate
else return "A Country with that name already exists";
}
//In your Country repository
Optional<Country> findByName(countryName);
In case my answer doesn't make sense, I have thrown together an example following my first suggestion (using the unique column attribute and a controller advice) which you can view/clone from here
Its better to ask using an example so...
An entity PurchaseOrder has a list of PurchaseOrderDetail. OneToMany Relation. Entity and list items cannot be directly saved used JPA methods as there is some calculations involved.
When we perform an update on PurchaseOrder we have to preserve the primary key so we can update like this
PurchaseOrder purchaseOrder = purchaseOrderRepository.getOne(purchaseOrderRequest.getId());
purchaseOrder.setSupplier(purchaseOrderRequest.getSupplier());
purchaseOrder.setDollarRate(purchaseOrderRequest.getDollarRate());
purchaseOrder.setShippingCost(purchaseOrderRequest.getShippingCost());
return purchaseOrderRepository.save(purchaseOrder);
But how to Beautifullyupdate the List<PurchaseOrderDetail> purchaseOrderDetails ?
I have the following code which will delete existing list items and will insert new items losing the primary keys. But what is the JPA way to do so that during update following happens
1. If some items were deleted they should be deleted from DB.
2. If some items were updated they should be updated preserving the primary keys.
3. If some new items are added they should be created having new primary keys.
Code
purchaseOrder.setPurchaseOrderDetails(null);
for (PurchaseOrderDetailRequest purchaseOrderDetailRequest : purchaseOrderRequest.getPurchaseOrderDetails())
{
PurchaseOrderDetail purchaseOrderDetail = new PurchaseOrderDetail();
purchaseOrderDetail.setPartNumber(purchaseOrderDetailRequest.getPartNumber());
purchaseOrderDetail.setDescription(purchaseOrderDetailRequest.getDescription());
purchaseOrderDetail.setQuantity(purchaseOrderDetailRequest.getQuantity());
purchaseOrderDetail.setUnitPriceInDollars(purchaseOrderDetailRequest.getUnitPriceInDollars());
purchaseOrderDetail.setTotalPriceInDollars(purchaseOrderDetailRequest.getQuantity() * purchaseOrderDetailRequest.getUnitPriceInDollars());
purchaseOrderDetail.setUnitPriceInSAR(purchaseOrder.getDollarRate() * purchaseOrderDetailRequest.getUnitPriceInDollars());
purchaseOrderDetail.setTotalPriceInSAR(purchaseOrderDetailRequest.getQuantity() * purchaseOrderDetail.getUnitPriceInSAR());
purchaseOrderDetail.setUnitCost(purchaseOrderDetail.getUnitPriceInSAR() + shippingFactor);
purchaseOrder.addPurchaseOrderDetail(purchaseOrderDetail);
}
PurchaseOrder
#Entity
#Table(name = "purchaseOrders")
public class PurchaseOrder extends UserDateAudit
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 140)
private String supplier;
#NotNull
private Float dollarRate;
#NotNull
private Float amount;
#NotNull
private Float shippingCost;
#OneToMany(
mappedBy = "purchaseOrder",
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true
)
#Fetch(FetchMode.SELECT)
#BatchSize(size = 150)
private List<PurchaseOrderDetail> purchaseOrderDetails = new ArrayList<>();
}
PurchaseOrderDetail
#Entity
#Table(name = "purchaseOrderDetails")
public class PurchaseOrderDetail
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 50)
private String partNumber;
#Size(max = 256)
private String description;
#NotNull
private Integer quantity;
#NotNull
private Float unitPriceInDollars;
#NotNull
private Float totalPriceInDollars;
#NotNull
private Float unitPriceInSAR;
#NotNull
private Float totalPriceInSAR;
#NotNull
private Float unitCost;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "purchaseOrder_id", nullable = false)
private PurchaseOrder purchaseOrder;
}
JpaRepository.save() does em.persist() or em.merge() according to the entity state.
And you also configured the mapping of the #OneToMany relationship with cascade = CascadeType.ALL and orphanRemoval = true, so you are in the right way.
What you have to do in your code is updating the PurchaseOrderDetail elements of the List defined in PurchaseOrder instead of creating new instances for them.
You could add the existing elements in a Map to fast up the lookup processing if required.
// collect existing details into a map to fast up
Map<Long, PurchaseOrderDetail> purchaseOrderDetailByIdMap =
purchaseOrder.getPurchaseOrderDetails()
.stream()
.collect(toMap(PurchaseOrderDetail::getId, p -> p);
//collect new details into a list
List<PurchaseOrderDetail> updatedDetails =
purchaseOrderRequest.getPurchaseOrderDetails()
.stream()
.map(detailReq -> mapDetailRequestToDetail(detailReq,
purchaseOrderDetailByIdMap))
.collect(toList());
// overwrite the relationship with the updatedDetails var
purchaseOrder.setPurchaseOrderDetails(updatedDetails);
// ...
// mapping function extracted to be clearer
private PurchaseOrderDetail mapDetailRequestToDetail(PurchaseOrderDetailRequest purchaseOrderDetailRequest,
Map<Long, PurchaseOrderDetail> purchaseOrderDetailByIdMap) {
// Here you get the existing element with the defined id or you create a new one
PurchaseOrderDetail purchaseOrderDetail =
purchaseOrderDetailByIdMap.computeIfAbsent(purchaseOrderDetailRequest.getId(), k-> new PurchaseOrderDetail());
// set fields
purchaseOrderDetail.setPartNumber(purchaseOrderDetailRequest.getPartNumber());
purchaseOrderDetail.setDescription(purchaseOrderDetailRequest.getDescription());
purchaseOrderDetail.setQuantity(purchaseOrderDetailRequest.getQuantity());
purchaseOrderDetail.setUnitPriceInDollars(purchaseOrderDetailRequest.getUnitPriceInDollars());
purchaseOrderDetail.setTotalPriceInDollars(purchaseOrderDetailRequest.getQuantity() * purchaseOrderDetailRequest.getUnitPriceInDollars());
purchaseOrderDetail.setUnitPriceInSAR(purchaseOrder.getDollarRate() * purchaseOrderDetailRequest.getUnitPriceInDollars());
purchaseOrderDetail.setTotalPriceInSAR(purchaseOrderDetailRequest.getQuantity() * purchaseOrderDetail.getUnitPriceInSAR());
purchaseOrderDetail.setUnitCost(purchaseOrderDetail.getUnitPriceInSAR() + shippingFactor);
return purchaseOrderDetail;
}
I am experiencing a problem with hibernate and lazy loading of objects.
basically I want to load an class which has an eagerly loaded field and not load the lazy fields of child classes
Take the following QuestionVO class
#Entity
#Table(name = "question")
public class QuestionVO extends BaseDAOVO implements Serializable {
/**
*
*/
private static final long serialVersionUID = -5867047752936216092L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#Column(name = "questionText", unique = false, nullable = false, length = 4000)
#Size(min = 3, max = 4000)
#Pattern(regexp = MobileAppsRegexConstants.GENERAL_ALLOWED_CHARCHTERS, message = "Question Text Not valid.")
private String questionText;
#ManyToOne(fetch = FetchType.EAGER)
#Cascade({ CascadeType.SAVE_UPDATE })
#JoinColumn(name = "MENU_STYLE_ID", nullable = true)
private MenuStyleVO menuStyle;
}
Take the following MenuStyleVO class
#Entity
#Table(name = "menu_style")
public class MenuStyleVO extends BaseDAOVO implements Serializable{
/**
*
*/
private static final long serialVersionUID = 3697798179195096156L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#Column(name = "menuStyleName", unique = false, nullable = false, length = 200)
private String menuStyleName;
#Column(name = "menuTemplate", unique = false, nullable = false, length = 200)
private String menuTemplate;
#OneToOne(fetch = FetchType.LAZY, optional=false)
#Cascade({ CascadeType.SAVE_UPDATE })
#JoinColumn(name="logo_id")
#JsonProperty("logo")
private ApplicationImageVO logo;
}
And this ApplicationImageVO class
#Entity
#Table(name = "application_image")
public class ApplicationImageVO extends BaseDAOVO implements Serializable {
/**
*
*/
private static final long serialVersionUID = -9158898930601867545L;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "image1242x2208")
#Cascade({ CascadeType.ALL })
#JsonIgnore
private SubmissionLauncherImagesVO launcherImage1242x2208;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "image1536x2048")
#Cascade({ CascadeType.ALL })
#JsonIgnore
private SubmissionLauncherImagesVO launcherImage1536x2048;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "image2048x1536")
#Cascade({ CascadeType.ALL })
#JsonIgnore
private SubmissionLauncherImagesVO launcherImage2048x1536;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "logo")
#Cascade({ CascadeType.ALL })
#JsonIgnore
private MenuStyleVO menuStyleLogo;
}
If L load the QuestionVO class from the database using the following hibernate criteria code - all the lazy fields of MenuStyleVO and ApplicationImageVO are also loaded.
On complicated use cases, this results in this query getting very slow
public QuestionVO findMasterAppQuestionById(int id) {
Criteria criteria = currentSession().createCriteria(QuestionVO.class);
criteria.add(Restrictions.eq("id", id));
QuestionVO questionVO = (QuestionVO) criteria.uniqueResult();
return questionVO;
}
What I am wondering is - would it be possible to load the QuestionVO class and its eager fields and tell hibernate to ignore lazy fields from the other classes bar those that are needed?
Cheers
Damien
Last time we faced an issue like this we used a constructor on parent class, which use only the desired fields of determined query.
I can't remember in fully how constructor inside a jpql query works, but it must be something like this:
select new com.package.class(c.field1, c.field2) from com.package.class c
Remember, a constructor with same arguments must be present on the desired entity.
Pros:
- Better query perfomance;
- Can be replicated with other arguments;
Cons:
- Pretty limited, you can only use this hack on the main entity you are querying;
- Includes a constructor only for determined query, poor design;
Also, you should take a look on EnttyGraphs of JPA. Seems quite promising, but didn't work as desired in our project.
Btw, Hibernate has put us many times on performance issues, hope this hack help you, good luck!
Edit:
Why this pattern would help in performance issues?
Basically, with the example i've showed before, you are not loading everything via Hibernate, only the two fields (field1 and field2) of the main entity. Without using a constructor you shoudn't be able to do that, because your query would not result in a collection of the desired entity, but in a collection of two objects each iteration (Object[]). Using the constructor pattern you are creating instances of the desired entity, but only selecting a few fields from database, and that's why this pattern can help you, you are returning a collection of the desired entity with only a few fields.
I have these two domains:
Category
#Entity
#Table(name = "CATEGORY")
public class Category extends Domain {
private static final long serialVersionUID = -2592988320455126109L;
#Id
#Column(name = "CATEGORY_CODE", length = 20, nullable = false)
#Size(min = 1, max = 20, message = "The category code size is not within limits (0-20)")
#NotNull(message = "The category code cannot be null")
private String code;
#Column(name = "CATEGORY_DESCRIPTION", length = 200, nullable = false)
#Size(min = 1, max = 200, message = "The category description is not within limits (0-200)")
#NotNull(message = "The category description cannot be null")
private String description;
#ManyToMany
#JoinTable(
name = "SOLUTION_CATEGORY_MAPPING",
joinColumns = #JoinColumn(
name = "CATEGORY_CODE",
referencedColumnName = "CATEGORY_CODE",
foreignKey = #ForeignKey(name = "FK_SOLUTION_CATEGORY_CATEGORY_CODE")),
inverseJoinColumns = #JoinColumn(
name = "SOLUTION_CODE",
referencedColumnName = "SOLUTION_CODE",
foreignKey = #ForeignKey(name = "FK_SOLUTION_CATEGORY_SOLUTION_CODE")))
private List<Solution> solutions;
and Solution
#Entity
#Table(name = "SOLUTION")
public class Solution extends Domain {
private static final long serialVersionUID = -8222656642529326612L;
#Id
#Column(name = "SOLUTION_CODE", length = 20, nullable = false)
#Size(min = 1, max = 20, message = "The solution code size is not within limits (0-20)")
#NotNull(message = "The solution code cannot be null")
private String code;
#Column(name = "SOLUTION_DESCRIPTION", length = 200, nullable = false)
#Size(min = 1, max = 200, message = "The solution description is not within limits (0-200)")
#NotNull(message = "The solution description cannot be null")
private String description;
#ManyToMany(mappedBy = "solutions")
private List<Category> categories;
Based on the annotations another table will be created in the database called SOUTION_CATEGORY_MAPPING which basically maps the solutions to the categories. The fetch is lazy.
In a page I submit the primary keys for one object each and attempt to create an entry in the mapping table.
After having fetched the objects/entries corresponding to the primary keys I call this method:
private void insertRelationship(CategoryKey categoryKey, SolutionKey solutionKey) {
Solution solutionToBeEdited = solutionAccess.fetchByPrimaryKey(solutionKey);
Category categoryToBeEdited = categoryAccess.fetchByPrimaryKey(categoryKey);
//The category is the owing side so I assume that as the start of all the thinking
categoryToBeEdited.addSolution(solutionToBeEdited);
solutionAccess.saveOrUpdate(solutionToBeEdited);
categoryAccess.saveOrUpdate(categoryToBeEdited);
}
In Category the relevant method is
public void addSolution(Solution solutionAdded) {
//TODO make checks that the solution doesn't already exist. List allows duplicate entries
if (solutions == null) {
solutions = new ArrayList<Solution>(1);
}
this.solutions.add(solutionAdded);
if(!solutionAdded.getCategories().contains(this)) {
solutionAdded.addCategory(this);
}
}
and in Solution this
protected void addCategory(Category categoryAdded) {
//TODO Check for duplicate blah blah blah
if (categories == null) {
categories = new ArrayList<Category>(1);
}
this.categories.add(categoryAdded);
//This is not needed if i make this protected and always work from the category side
if(!categoryAdded.getSolutions().contains(this)) {
categoryAdded.addSolution(this);
}
}
The save or update basically calls entityManagerProvider.get().persist(object) somewhere down the levels (my Access level (DAO)).
So if I run the servlet and this path follows I can see in my SOLUTION_CATEGORY_MAPPING table an entry |solution|category|.
However, if in the same page then I try to fetch the category or solution entry, the list member in the object is null. Shouldn't it bring back something? Or since it's lazy fetch I have to do something to make hibernate retrieve the actual data from the table?
Any help, pointers, suggestions appreciated.
I have a USER table associated with many other tables, in general, star topology.
Like this:
#Entity
#Table(name = "user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "user_USERID_GENERATOR", sequenceName = "user_SEQ")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "userR_USERID_GENERATOR")
#Column(name = "user_id")
private long userId;
#Basic
#Column(name = "password_hex")
private String password;
#Basic
#Column(name = "language")
private String language;
#Temporal(TemporalType.DATE)
private Date created;
#Temporal(TemporalType.DATE)
private Date modyfied;
#Basic
#Column(name = "first_name")
private String firstName;
#Basic
#Column(name = "last_name")
private String lastName;
#Basic
#Column(name = "passport")
private String passport;
#Basic
#Column(name = "pesel")
private String pesel;
#Basic
#Column(name = "phone_nr1")
private String phoneNr1;
#Basic
#Column(name = "phone_nr2")
private String phoneNr2;
#Column(name = "hash")
private String hash;
// uni-directional many-to-one association to DictUserType
#ManyToOne
#JoinColumn(name = "status")
private DictUserStatus status;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
private Set<Email> emails = new HashSet<Email>(0);
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
private Set<Address> address = new HashSet<Address>(0);
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
private Set<ArchivePasswords> archivePasswords = new HashSet<ArchivePasswords>(
0);
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
private Set<HostsWhitelist> hostsWhitelist = new HashSet<HostsWhitelist>(0);
....
I have a DAO layer, the method of search by user ID.
public User findUser(long userId) throws UserNotFoundException {
User user = userDao.findUser(userId);
if (user == null) {
throw new UserNotFoundException("Could not find user with id = "
+ userId);
}
return user;
}
Why lazy fetching does not work?
You should post the stack trace you are receiving. Where is the LazyLoadingException occurring? On the user Object? Are you trying to access it from another Object?
Is this the notorious LazyInitializationException? If so, then you need to either traverse the Object graph manually in the service (assuming you DAO code snippet is actually a Service method and not the DAO itself), or research the OpenSessionInViewFilter (assuming you are using Spring).
If you want to fetch the user with emails.
#Transactional
public List getUserWithEmails(long userId){
User user = userDao.findUser(userId);
if (user == null) {
throw new UserNotFoundException("Could not find user with id = "
+ userId);
}
for(Email email:user.getEmails()){
email.getId();
}
return user;
}
The same procedure apply to other one-to-many sets. Just like others have stated, you need to add OpenSessionInView (Hibernate) filter or OpenEntityManagerInView (JPA)filter in web.xml
If not specified, lazy fet hing will not take place defaults to EAGER.
public #interface Basic
The simplest type of mapping to a database column. The Basic annotation can be applied to a persistent property or instance variable of any of the following types: Java primitive types, wrappers of the primitive types, String, java.math.BigInteger, java.math.BigDecimal, java.util.Date, java.util.Calendar, java.sql.Date, java.sql.Time, java.sql.Timestamp, byte[], Byte[], char[], Character[], enums, and any other type that implements java.io.Serializable.
The use of the Basic annotation is optional for persistent fields and properties of these types. If the Basic annotation is not specified for such a field or property, the default values of the Basic annotation will apply.
Example 1:
#Basic
protected String name;
Example 2:
#Basic(fetch=LAZY)
protected String getName() { return name; }
fetch
public abstract FetchType fetch
(Optional) Defines whether the value of the field or property should be lazily loaded or must be eagerly fetched. The EAGER strategy is a requirement on the persistence provider runtime that the value must be eagerly fetched. The LAZY strategy is a hint to the persistence provider runtime. If not specified, defaults to EAGER.
Default:
javax.persistence.FetchType.EAGER
optional
public abstract boolean optional
(Optional) Defines whether the value of the field or property may be null. This is a hint and is disregarded for primitive types; it may be used in schema generation. If not specified, defaults to true.
Default:
true