I'm new on publishing something on Stackoverflow but I use it for along time and it was amazing helping me coding.
My problem was the follow:
I receive a object filled in a WebService by SOAP like:
#WebMethod(operationName = "DogUpdate")
public #WebResult(name = "resultId")
Long dogUpdate(
#WebParam(name = "DogDto", header = true, mode = Mode.IN) DogDto dog);
So now I need to update on DB using Hibernate 5.0.2.
The class DogDto is like this:
Entity
#Table(name="Dog", indexes={}, uniqueConstraints={})
class Dog implements Dog_i
{
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="DOG_SEQ")
#SequenceGenerator(name="DOG_SEQ", sequenceName="DOG_SEQ", allocationSize=1)
#Id
#Column(name="ID", unique=true, nullable=false)
private Long id;
#Column(name="Name", length=30)
private String name;
#Column(name="BirthDate")
private Date birthDate;
#Column(name="NumberDogSons")
private Long numberDogSons;
#Column(name="namesDogSons")
private List<String> namesDogSons;
}
So my problem is I doesnt know what is the values of the DogDto I receive in my WebService, so I doesnt know what I need to change.
I saw that's Reflection can helps but I have a complex object like (List inside the object dogs and like 'Owner' and others more)
What I can do update the object with info not lost? Because if a field cames null (when is a Long, Integer, int...) it shows like 0 and other relevant problems.
Do you have any suggestion?
Many thanks for everyone :)
So my problem is I doesnt know what is the values of the DogDto I receive in my WebService, so I doesnt know what I need to change.
From you code above, you seem to get a "DogDto dog".
You just have to make hibernate persist this dog object using saveOrUpdate method.
If your ID is well defined Hibernate will update the existing if it exists, otherwise it will insert a new one.
Related
I'm learning Spring and few days ago i started learning Hibernate. I have my studying project where i need to create a shop with products. Here is entity class
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
#Entity
#Table(name = "cart")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Cart {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "quantity")
private int quantity;
#Column(name = "mask")
private String mask;
#Column(name = "price")
private int price;
So, i create interface
import org.springframework.data.jpa.repository.JpaRepository;
public interface CartRepository extends JpaRepository<Cart, Integer> {
}
and create controllers
#Autowired
CartRepository cartList;
#RequestMapping("/add-black-mask")
public String addBlackMask() {
cartList.save(new Cart(1, 1, "black", 3));
return "masks/add-black-mask";
}
#RequestMapping("/add-build-mask")
public String addBuildMask() {
cartList.save(new Cart(2, 1, "build", 5));
return "masks/add-build-mask";
}
#RequestMapping(value = "/save_product_to_cart")
public ModelAndView saveProduct(#ModelAttribute(value = "cart")
Cart cart, BindingResult result) {
int index = Integer.parseInt(String.valueOf(cartList.count()));
if (cart.getId() == 0) {
cart.setId(index + 1);
cartList.save(cart);
} else {
Cart cart1 = cartList.getOne(cart.getId());
cart1.setMask(cart.getMask());
cart1.setQuantity(cart.getQuantity());
cart1.setPrice(cart.getPrice());
cartList.save(cart1);
}
return new ModelAndView("redirect:/");
}
Also, there are some other controllers for view, Thymeleaf etc, but its ok. My problem is - when i save my product 1 time - its ok, but when i save second - it didnt work( i think because i can't save 2 rows with similar ID) So it seems i have UNIQUE ID in my table and it can not be repeated. Question is - how can i delete unique id or change my code in any way? Thanks in advance!
p.s. i read some other topics here but it didnt help me.
when you use GENERATIONTYPE.IDENTITY you are asking hibernate to let the database handle Ids for you, you should not set it yourself. you are changing the value of the id, just create a new product, set all the fields and inside a transactional context, save your product. Also always use wrapped versions of primitives for serialization purposes. (Long is an object but long is a primitive.) you can google boxing and unboxing and learn more about this.
Let me answer this question:
First of all, using annotations as our configure method is just a convenient method instead of coping the endless XML configuration file.
The #Idannotation is inherited from javax.persistence.Id, indicating the member field below is the primary key of current entity. Hence your Hibernate and spring framework as well as you can do some reflect works based on this annotation. for details please check javadoc for Id
The #GeneratedValue annotation is to configure the way of increment of the specified column(field). For example when using Mysql, you may specify auto_increment in the definition of table to make it self-incremental, and then use
#GeneratedValue(strategy = GenerationType.IDENTITY)
in the Java code to denote that you also acknowledged to use this database server side strategy. Also, you may change the value in this annotation to fit different requirements.
1. Define Sequence in database
For instance, Oracle has to use sequence as increment method, say we create a sequence in Oracle:
create sequence oracle_seq;
2. Refer the database sequence
Now that we have the sequence in database, but we need to establish the relation between Java and DB, by using #SequenceGenerator:
#SequenceGenerator(name="seq",sequenceName="oracle_seq")
sequenceName is the real name of a sequence in Oracle, name is what you want to call it in Java. You need to specify sequenceName if it is different from name, otherwise just use name. I usually ignore sequenceName to save my time.
3. Use sequence in Java
Finally, it is time to make use this sequence in Java. Just add #GeneratedValue:
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq")
The generator field refers to which sequence generator you want to use. Notice it is not the real sequence name in DB, but the name you specified in name field of SequenceGenerator.
4. Complete
So the complete version should be like this:
public class Cart
{
#Id
#SequenceGenerator(name="seq",sequenceName="oracle_seq")
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq")
private Integer id;
}
Now start using these annotations to make your JavaWeb development easier.
On top of that I would like you to understand all 4 ways of ID generation in hibernate. You can think of reading in your free time
GenerationType.AUTO
GenerationType.IDENTITY (your case)
GenerationType.SEQUENCE
GenerationType.TABLE {Rarely used nowdays}
I am using JPA and have a view I would like to access. I am using a mapped entity with an embedded Id in many of my other classes to access tables with similar requirements. However here whenever there are nulls in the view that comprise the id, the whole object is returned as null. There are the right number of entities returned when i query, but they are null.
Here are the classes:
{
#Entity
#Table(name = "VW_PRODUCT")
public class VwProduct implements Serializable {
#EmbeddedId
private VwProductId id;
public VwProduct() {
}
}
{
#Embeddable
public class VwProductId implements java.io.Serializable {
#Column(name = "PROD_NAME", nullable=true)
private String prodName;
#Column(name = "PROD_CTGRY", nullable=true)
private String prodCtgry;
#Column(name = "PROD_SBCTGRY", nullable=true)
private String prodSbctgry;
}
I omitted things like getters and setters and hashcode but i think my question is clear; how do I access this view, when some of its values are null?
Thank you!
If you have a primary key column with null, and search on that column will always return no objects.
There are three possible solutions that I am aware of, in order of complexity/wonkiness. (For people not working on a read-only view, do not do any of the following. You will blow your foot off with an incredibly large shotgun.)
The easiest answer is to change your definition of the view and add something like a rowid or generated serial and then make that the primary key.
The second answer, and this is both implementation specific and hibernate specific, is to have a primary key of #ID #ROWID String id;
The last answer, is more complex, is by mapping all three of your fields to a "NullableString" UserType, and have a nullSafeGet that maps null to something non-null, like '' or something. You'll get duplicates, but since this is a read-only view, you don't really care.
I know there's already a similar question answered previously, but my problem is implementing save with update while there are 3 methods in the interface.
I'm currently using the following methods in my project and don't know how to make saveOrUpdate in this.
The following are my classes:
public interface CompanyRepository extends CrudRepository<Company,Long>{
Company findByCompanyName (String companyName);
List<Company> findAll();
Company findById(Long id);
}
The following is part of my Company Class
#Entity
public class Company extends BaseEntity{
#NotNull
#Size(min = 2, max = 16)
private String companyName;
#Length(max = 60)
private String desc;
//TODO max: add LOGO class later for pic saving
#OneToMany
private List<MobileModel> mobileModels;
public Company()
{
super();
mobileModels = new ArrayList<>();
}
//Getters n setters
}
The following is my baseEntity clas
#MappedSuperclass
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected final Long id;
#Version
private Long version;
//Getters n setters
}
Thanks in advance.
I read everywhere and tried so many things for 5 hours.
I just want CompanyRepository to implement all 3 methods without me overriding them in some other class but if I have too then explain how because part of my code is dependent on CompanyRepository. I just wish to add save with update, please explain with respect to my code.
CrudRepository has only save but it acts as update as well.
When you do save on entity with empty id it will do a save.
When you do save on entity with existing id it will do an update that means that after you used findById for example and changed something in your object, you can call save on this object and it will actually do an update because after findById you get an object with populated id that exist in your DB.
save in CrudRepository can accept a single entity or Iterable of your entity type.
putting below if check resolve my issue and save method is working as save and update both when i pass id it updates the record in database and when i dont pass id it save new record in database
place is incoming object in my case and new place is new object of place in which i am setting the place id
if(place.getPlaceId()!=0)
newPlace.setPlaceId(place.getPlaceId());
I do have a simple entity named "Address" that has a couple of properties and relations defined by itself and some inherited from some superclass.
public class Base {
private UUID id;
private Date createdAt;
#NotNull
private User createdBy; // User is a related Entity that cannot be null
private DataImport dataImport; // DataImport is another related entity that can be null
//getter & setter
}
public class Address extends Base {
private String street;
#NotNull
private Country country; // related entity that can't be null
//getter & setter
}
What I'm trying to achieve is with one query using the Criteria API, I want to get a list of Address objects containing all simple attributes like street and createdAt. At the same time I want only the IDs of the related entities if present: createdBy.id, dataImport.id and country.id.
I'm almost there using the following Criteria query:
entityManager.getDelegate().createCriteria(Address.class).add(criteriaExample)
.excludeZeroes()
.createAlias("createdBy", "subcreatedBy")
.createAlias("country", "subcountry")
.setProjection(Projections.projectionList().add(Projections.property("id").as("id"))
.add(Projections.property("createdAt").as("createdAt"))
.add(Projections.property("street").as("street")).
.add(Projections.property("subcreatedBy.id").as("createdBy.id"))
.add(Projections.property("subcountry.id").as("country.id")))
.setResultTransformer(new AliasToBeanNestedResultTransformer(Address.class));
List<Address> result = criteria.list();
This works just perfect!
Problem occurs when I only add the "alias" for the dataImport relation.
...createAlias("dataImport", "subdataImport")...
Even without adding the Projection for dataImport.id to the query, it returns an empty list, meaning list.size() = 0, as soon as I add this alias.
My current guess is, that I can't put an alias on a nullable property. Does anybody have an idea what the solution might be? So, when the related entity is not null, I want to get it's ID. And I want it to be simply null, when the relation is not set.
Thanks in advance.
Stupid me should have read the documentation and set the CriteriaSpecification.LEFT_JOIN.
...createAlias("dataImport", "subdataImport", CriteriaSpecification.LEFT_JOIN)...
Here is a simplified POJO i have:
#Entity
#Table( name = "Patient" )
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn
(
name="Discriminator",
discriminatorType=DiscriminatorType.STRING
)
#DiscriminatorValue(value="P")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Patient implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "ID", unique = true, nullable = false)
protected Integer ID;
#ManyToOne(targetEntity = TelephoneType.class, fetch=FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="IDPhoneType")
protected TelephoneType phoneType;
#JsonProperty(required=false, value="phoneType")
public TelephoneType getPhoneType() {
return phoneType;
}
public void setPhoneType(TelephoneType phoneType) {
this.phoneType = phoneType;
}
}
Now here is my class TelephoneType:
#Entity
#Table( name = "TelephoneType" )
#Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
#JsonAutoDetect(getterVisibility=Visibility.NONE, isGetterVisibility=Visibility.NONE, fieldVisibility=Visibility.NONE)
public class TelephoneType implements Serializable{
private static final long serialVersionUID = -3125320613557609205L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "ID", unique = true, nullable = false)
private Integer ID;
#Column(name = "Name")
private String name;
#Column(name = "Description")
private String description;
public TelephoneType() {
}
#JsonProperty(value="id")
public int getID() {
return ID;
}
public void setID(int iD) {
ID = iD;
}
#JsonProperty(value="name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#JsonProperty(value="description")
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
The reason i use the #JsonAutoDetect annotation in TelephoneType is first to customize the json property names (i needed to deactivate the default jsonautodetect) and also because if I don't, I get an error when fetching the Queue
No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: my.package.Patient["phoneType"]->my.package.TelephoneType_$$_jvste17_13["handler"])
So without the #JsonAutoDetect annotation i get the error and with the annotation no Lazy Loading occurs and the TelephoneType is always loaded in the json response.
I use Criteria to make the query:
return this.entityManager.find(Patient.class, primaryKey);
I also added, as I read in different posts on so, the following in the web.xml of my application (Jersey API):
<filter>
<filter-name>OpenEntityManagerInViewFilter</filter-name>
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>OpenEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Now somehow I surely missed something in my configuration but can't figure out what and we have many #ManyToOne relationships in the db that are slowing down the api considerably (some heavier objects than the one I showed in the example) so I would really appreciate to find a way to activate this lazy loading thing...
If you are using JSON then I presume that you are supplying the results through a REST endpoint. What is happening then is you are passing the Patient entity back to the REST service. When the REST service, Jersey in this case, serializes the Patient entity it touches all of the properties, and even walks through them, so as to build as complete a tree as possible. In order to do this, every time Jersey hits a property that's not yet initialized, Hibernate makes another call back to the database. This is only possible if the EntityManager is not yet closed.
This is why you have to have the OpenEntityManagerInViewFilter installed. Without it, the EntityManager is closed when you exit the service layer and you get a LazyInitializationException. The OpenEntityManagerInViewFilter opens the EntityManager at the view level and keeps it open until the HTTP request is complete. So, while it seems like a fix, it's not really because, as you see, when you lose control over who is accessing the properties of your entities, in this case Jersey, then you end up loading things you didn't want to load.
It's better to remove the OpenEntityManagerInViewFilter and figure out what exactly you want Jersey to serialize. Once you have that figured out, there are at least two ways to go about handling it. IHMO, the "best practice" is to have DTO, or Data Transfer Objects. These are POJOs that are not entities but have pretty much the same fields. In the case, the PatientDTO would have everything except the phoneType property (or maybe just the Id). You would pass it a Patient in the constructor and it would copy the fields you want Jersey to serialize. Your service layer would then be responsible for returning DTO's instead of Entities, at least for the REST endpoints. Your clients would get JSON graphs that represent these DTOs, giving you better control over what goes into the JSON because you write the DTOs separate from the Entities.
Another option is to use JSON annotations to prevent Jersey from attempting to serialize properties you don't want serialized, such as phoneType, but that ultimately becomes problematic. There will be conflicting requirements and you never get it sorted out well.
While making DTO's at first seems like a horrible pain, it's not as bad as it seems and it even helps when you want to serialize values that are more client friendly. So, my recommendation is to lose the OpenEntityManagerInViewFilter and construct a proper service layer that returns DTOs, or View Objects as they are sometimes called.
References: What is Data Transfer Object?
REST API - DTOs or not?
Gson: How to exclude specific fields from Serialization without annotations
To understand what is happening here you have to understand how lazy loading works in Hibernate.
When a list is declared as "lazy loaded" the Hibernate framework implements a "lazy loaded" JavassistLazyInitializer object with Javassist.
Hence, the phoneType on your patient object is not an implementation of your TelephoneType class. It is a proxy towards it.
When getPhoneType() on this object is called however, the proxy on patient is replaced by the real object.
Unfortunately, #JsonAutoDetect uses reflection on the proxy object without ever calling getPhoneType() and tries to actually serialise the JavassistLazyInitializer object which of course is impossible.
I think the most elegant solution for this is to implement a query that fetches the patients with their telephoneType.
So instead of:
return this.entityManager.find(Patient.class, primaryKey);
Implement something like:
EntityManager em = getEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Patient> query = cb.createQuery(Patient.class);
Root<Patient> c = query.from(Patient.class);
query.select(c).distinct(true);
c.fetch("phoneType");
TypedQuery<Patient> typedQuery = em.createQuery(query);
List<Patient> allPatients = typedQuery.getResultList();
Adapting the query to your needs as required.
Have a look at jackson-datatype-hibernate
It makes the json serialization with jackson 'aware' of the hibernate proxy