In my domain model I have following Classes.A 'UserProfile' has one 'SecurityPrincipal'
class SecurityPrincipal{
private String loginId;
private String password;
private Date registeredData;
private int status;
}
class UserProfile {
private String name;
private String company;
private SecurityPrincipa principal
}
I want to get the sorted results of 'UserProfile' objects and it works fine for simple properties.Like
DetachedCriteria criteria=DetachedCriteria.forClass(UserProfile.class);
criteria.addOrder(Order.asc("name");
But when I try to access properties of inner bean (SecurityPrincipal instance) like
criteria.addOrder(Order.asc("principal.status");
Hibernate gives the error:
Caused by:
org.hibernate.QueryException: could
not resolve property:
securityPrincipal.status of:
com.bigg.ibmd.usermanagement.model.UserProfile
at
org.hibernate.persister.entity.AbstractPropertyMapping.propertyException(AbstractPropertyMapping.java:44)
at
org.hibernate.persister.entity.AbstractPropertyMapping.toColumns(AbstractPropertyMapping.java:59)
at
org.hibernate.persister.entity.BasicEntityPropertyMapping.toColumns(BasicEntityPropertyMapping.java:31)
How can I sort my results by properties-of-a-property?
Thanks
Try this:
DetachedCriteria criteria=DetachedCriteria.forClass(UserProfile.class);
criteria.createAlias("principal", "p");
criteria.addOrder(Order.asc("p.name"));
I haven't tried it, nor am I sure that it's the nicest way, but I think it should work.
For filtering by multiple fields you ought to use #OrderBy annotation. For example:
#OrderBy("id, name, whatever")
private SecurityPrincipa principal
...
Pay attention JPA Criteria api 2.0 generates OUTER JOIN for collections. It means if you have not One to One but One to Many relation in the table it fetchs multiple results:
#OrderBy("weight, height")
private Collection userVitalStatsCollection;
To avoid duplicates it is handy to use Database View.
Related
I'm currently learning Spring-Boot and Spring-Data-JPA.
I'm using a postgresql database for storing the data.
My goal is to store ingredients with a unique and custom ID (you just type it in when creating it), but when another ingredient with the same ID gets inserted, there should be some kind of error. In my understanding, this is what happens when I use the #Id annotation, hibernate also logs the correct create table statement.
This is my Ingredient class:
public class Ingredient {
#Id
#Column(name = "ingredient_id")
private String ingredient_id;
#Column(name = "name")
private String name;
#Column(name = "curr_stock")
private double curr_stock;
#Column(name = "opt_stock")
private double opt_stock;
#Column(name = "unit")
private String unit;
#Column(name = "price_per_unit")
private double price_per_unit;
#Column(name = "supplier")
private String supplier;
-- ... getters, setters, constructors (they work fine, I can insert and get the data)
}
My controller looks like this:
#RestController
#RequestMapping(path = "api/v1/ingredient")
public class IngredientController {
private final IngredientService ingredientService;
#Autowired
public IngredientController(IngredientService ingredientService) {
this.ingredientService = ingredientService;
}
#GetMapping
public List<Ingredient> getIngredients(){
return ingredientService.getIngredients();
}
#PostMapping
public void registerNewStudent(#RequestBody Ingredient ingredient) {
ingredientService.saveIngredient(ingredient);
}
}
And my service class just uses the save() method from the JpaRepository to store new ingredients.
To this point I had the feeling, that I understood the whole thing, but when sending two post-requests to my application, each one containing an ingredient with the id "1234", and then showing all ingredients with a get request, the first ingredient just got replaced by the second one and there was no error or smth. like that in between.
Sending direct sql insert statements to the database with the same values throws an error, because the primary key constraint gets violated, just as it should be. Exactly this should have happened after the second post request (in my understanding).
What did I get wrong?
Update:
From the terminal output and the answers I got below, it is now clear, that the save() method can be understood as "insert or update if primary key is already existing".
But is there a better way around this than just error-handle every time when saving a new entry by hand?
The save method will create or update the entry if the id already exists. I'd switch to auto generating the ID when inserting, instead of manually creating the IDs. That would prevent the issue you have
When saving a new ingredient, jpa will perform an update if the value contained in the “id” field is already in the table.
A nice way through which you can achieve what you want is
ingredientRepository.findById(ingredientDTO.getIngredientId()).
ifPresentOrElse( ingredientEntity-> ResponseEntity.badRequest().build(), () -> ingredientRepository.save(ingredientDTO));
You can return an error if the entity is already in the table otherwise (empty lambda), you can save the new row
This is a downside to using CrudRepository save() on an entity where the id is set by the application.
Under the hood EntityManager.persist() will only be called if the id is null otherwise EntityManager.merge() is called.
Using the EntityManager directly gives you more fine grained control and you can call the persist method in your application when required
I have a 1:N relationship where a Victim might have lots of EmergencyContacts.
I've created a DTO called VictimDTO and inside of it there's a List and I'm using ModelMapper to convert my DTO into an Entity.
When I get rid of the list just for testing purposes, it works fine placing this code snippet inside of the insert "POST" method in the Controller layer in Java:
VictimEntity victimEntity = modelMapper.map(victimDTO, VictimEntity.class);
But now I've placed this List inside of my VictimDTO and I'm getting an Exception when trying to use this code.
My DTO actual code is this one as it follows below:
public class VictimDTO {
private String name;
private int age;
private String email;
private String phone;
private List<ContactDTO> contactDTOList;
//getters and setters
}
How can I use ModelMapper to convert a POJO with a Collection (List) as an instance variable inside into an Entity?
Thank you for your help.
Best regards,
Lucas Abrão
First, here are my entities.
Player :
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Player {
// other fields
#ManyToOne
#JoinColumn(name = "pla_fk_n_teamId")
private Team team;
// methods
}
Team :
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Team {
// other fields
#OneToMany(mappedBy = "team")
private List<Player> members;
// methods
}
As many topics already stated, you can avoid the StackOverflowExeption in your WebService in many ways with Jackson.
That's cool and all but JPA still constructs an entity with infinite recursion to another entity before the serialization. This is just ugly ans the request takes much longer. Check this screenshot : IntelliJ debugger
Is there a way to fix it ? Knowing that I want different results depending on the endpoint. Examples :
endpoint /teams/{id} => Team={id..., members=[Player={id..., team=null}]}
endpoint /members/{id} => Player={id..., team={id..., members=null}}
Thank you!
EDIT : maybe the question isn't very clear giving the answers I get so I'll try to be more precise.
I know that it is possible to prevent the infinite recursion either with Jackson (#JSONIgnore, #JsonManagedReference/#JSONBackReference etc.) or by doing some mapping into DTO. The problem I still see is this : both of the above are post-query processing. The object that Spring JPA returns will still be (for example) a Team, containing a list of players, containing a team, containing a list of players, etc. etc.
I would like to know if there is a way to tell JPA or the repository (or anything) to not bind entities within entities over and over again?
Here is how I handle this problem in my projects.
I used the concept of data transfer objects, implemented in two version: a full object and a light object.
I define a object containing the referenced entities as List as Dto (data transfer object that only holds serializable values) and I define a object without the referenced entities as Info.
A Info object only hold information about the very entity itself and not about relations.
Now when I deliver a Dto object over a REST API, I simply put Info objects for the references.
Let's assume I deliever a PlayerDto over GET /players/1:
public class PlayerDto{
private String playerName;
private String playercountry;
private TeamInfo;
}
Whereas the TeamInfo object looks like
public class TeamInfo {
private String teamName;
private String teamColor;
}
compared to a TeamDto
public class TeamDto{
private String teamName;
private String teamColor;
private List<PlayerInfo> players;
}
This avoids an endless serialization and also makes a logical end for your rest resources as other wise you should be able to GET /player/1/team/player/1/team
Additionally, the concept clearly separates the data layer from the client layer (in this case the REST API), as you don't pass the actually entity object to the interface. For this, you convert the actual entity inside your service layer to a Dto or Info. I use http://modelmapper.org/ for this, as it's super easy (one short method call).
Also I fetch all referenced entities lazily. My service method which gets the entity and converts it to the Dto there for runs inside of a transaction scope, which is good practice anyway.
Lazy fetching
To tell JPA to fetch a entity lazily, simply modify your relationship annotation by defining the fetch type. The default value for this is fetch = FetchType.EAGER which in your situation is problematic. That is why you should change it to fetch = FetchType.LAZY
public class TeamEntity {
#OneToMany(mappedBy = "team",fetch = FetchType.LAZY)
private List<PlayerEntity> members;
}
Likewise the Player
public class PlayerEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "pla_fk_n_teamId")
private TeamEntity team;
}
When calling your repository method from your service layer, it is important, that this is happening within a #Transactional scope, otherwise, you won't be able to get the lazily referenced entity. Which would look like this:
#Transactional(readOnly = true)
public TeamDto getTeamByName(String teamName){
TeamEntity entity= teamRepository.getTeamByName(teamName);
return modelMapper.map(entity,TeamDto.class);
}
In my case I realized I did not need a bidirectional (One To Many-Many To One) relationship.
This fixed my issue:
// Team Class:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Player> members = new HashSet<Player>();
// Player Class - These three lines removed:
// #ManyToOne
// #JoinColumn(name = "pla_fk_n_teamId")
// private Team team;
Project Lombok might also produce this issue. Try adding #ToString and #EqualsAndHashCode if you are using Lombok.
#Data
#Entity
#EqualsAndHashCode(exclude = { "members"}) // This,
#ToString(exclude = { "members"}) // and this
public class Team implements Serializable {
// ...
This is a nice guide on infinite recursion annotations https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
You can use #JsonIgnoreProperties annotation to avoid infinite loop, like this:
#JsonIgnoreProperties("members")
private Team team;
or like this:
#JsonIgnoreProperties("team")
private List<Player> members;
or both.
How is this called and how to solve my next problem on API. I have to return same object with different views. Some data should not be returned to user. Here is example:
Parent:
public class OrginalObject{
private int id;
private String name;
private String surname;
private int age;
private String school;
private String secret;
private Address Address;
child:
public class Address{
private int id;
private String street;
private String zipCode;
private String Country;
If i want to load list of complete objects i would call:
session.createCriteria(OrginalObject.class).list();
1.) But if don't want someone to know my property secret, i need to hide it. But i don't know how to call it from database the way it would have every other property. Something like:
session.createCriteria(OrginalObjectPublic.class).list();
2.) Also I would like to have option to load only "important" data. That means only properties id, name, school.
session.createCriteria(OrginalObjectImportant.class).list();
Is there a way to do an adapter/"custom view" to directly load it from database? I know i can write pure sql, but i would like to use it on objects with 20+ properties that have nested lists/objects.
3.) Also how to use this transformation to load only few properties of nested object with those from orginal. Example json (only id, name, school from OrginalOBject and id, street from Address:
{
"id": 1,
"name": "testname",
"school": "testschool",
"Address": {
{
"id": 33,
"street": "testStreet 33"
}
}
4.) also how to use it on nested Lists if Address would be array:
public class OrginalObject{
...
private List<Address> AddressList;
Since hibernate is a persistence-framework and you can not save/persist to a view, this is not possible. Yes you can make a view having the name like the table and preferr the view but you will not be able to store to that entity anymore.
You can remove the getter (getSecret) from the entity. So the database still have the field but your entity is not aware of it. This may cause problems if you try to store data using that entity, you may not be able to set the secret.
You can make the getter default (package-level-access) and seal the package to let noone else than the sealed projects access the getter.
You can use spring's method authorization mechanism
First no one have access to your secret,
only you the programmer who is supposed to see it.
Second if no one is supposed to have it why store it.
And if you want to pull it out you can use inheritance.
something like
public abstract PublicObject {
...
}
public OriginalObject extends PublicObject {
String secret;
}
Edit:
2nd & 4th questions you can solve them with hql:
String hql = "SELECT O.id, O.name, O.school FROM OrginalObject O";
Query query = session.createQuery(hql);
List results = query.list();
as for your 3th question it depends on your api. if you're using jackson for example you can use #JsonIgnore
You can map more than one entity to the same table, each one with the set of properties you want to expose.
Take a look to this question.
I'm using Hibernate with jpa and I'm getting LazyInizializationException trying to do a very simple thing. I know what does LazyInizializationException means but I can't understand why it comes while i'm doing everything in the most common and simple way. This is the "one" side of the relationship:
#Entity
public class User implements Serializable{
#Id #GeneratedValue
private int idUser;
private String name;
private String surname;
private String username;
#OneToMany(mappedBy="user")
private List<Device> dev;
...getters and setters...
and this is the "Many" side:
#Entity
public class Device implements Serializable {
#Id #GeneratedValue
private int idDevice;
private String brand;
private String model;
#ManyToOne
#JoinColumn(name="user_fk")
private User user;
...getters and setters...
the jUnit test that throws the exception is:
#Test
public void testLazyUserSnd() {
User u = uDao.getUser(2);
List<Device> devList = u.getDev();
Device aDevice = devList.get(0); // <--- Here the exception is thrown
aDevice.getModel();
I made the relationship as explained in the Hibernate Documentation. Any hint? Am I making some big and stupid mistakes?
While #Xavi's answer is perfectly reasonable, you may not always want to load the devices for a user. If you don't, there are 2 ways of fixing this.
Create an additional method uDao.getUserWithDevices(id) and call that when you know you need devices, otherwise call the uDao.getUser(id).
Encapsulate the test method, and therefore any production code that uses the method, in a transaction. In other words keep the session open as long as you need to.
Personally I'd use the transaction method since as it allows more flexibility and allows JPA to lazy load whenever it needs to. See also http://community.jboss.org/wiki/OpenSessionInView for more interesting information around session lifecycle.
The exception is telling you that you're trying to get some of the lazy-loaded association's elements when the session is closed. Probably you should call u.getDev() or Hibernate.initialize(u.getDev()) inside the dao's method, when the hibernate session is still open.
Or, if you're using Criteria, you could also use setFetchMode to force eager fetching.
public User getUser(String id) {
Session session = getSession();
Criteria criteriaQuery = session.createCriteria(User.class);
criteriaQuery.add(Expression.eq("id", id));
criteriaQuery.setFetchMode("dev", FetchMode.JOIN);
return criteriaQuery.uniqueResult();
}
The entity is probably detached from the session (transaction context) when you access the relation. Try to enclose your test method in a transaction.