I have a post class and it kind of works, but there's one problem: the primary key doesn't increase.
#Entity
#Table(name="posts")
public class Post extends GenericModel{
#Id
#Column(name="post_id")
public int id;
#Column(name="post_situation")
public String situation;
#Column(name="post_date")
public Date date;
#Column(name="post_userid")
public int userid;
#OneToMany(mappedBy="post", cascade=CascadeType.ALL)
public List<Block> blocks;
public Post addBlock(String content, int position){
Block b = new Block(this, content, position);
b.save();
this.blocks.add(b);
this.save();
return this;
}
public Post(String situation, Date date){
this.situation = situation;
this.date = date;
this.userid = 2;
}
}
When I call it the first time on an empty table, it works fine, but the second time, I'm getting PersistenceException occured : org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
The post_id column always has 0. Any idea how to fix this? I have the #Id annotation in palce..
This is how I have in my controller:
Post p = new Post("Midden in het middenoosten.", new Date()).save();
Any ideas what's causing this problem?
It seems that you want the primary key values to be auto-generated. If that is the case, if you'll need to add the #GeneratedValue annotation to the id attribute, in addition to the #Id annotation. Your code should therefore be:
#Id
#Column(name="post_id")
#GeneratedValue
public int id;
There are several strategies available to generate the Ids. You would have to read up on those to decide if you want to choose the TABLE -based, SEQUENCE -based or the IDENTITY -based strategy (which depends on what your database supports). If you choose a strategy explicitly, the define strategy will be used, instead of the default AUTO strategy. Explicit strategy decisions, are communicated in code as:
#Id
#Column(name="post_id")
#GeneratedValue(strategy=SEQUENCE, generator="POST_SEQ")
public int id;
Without generated values, the default value for integers in Java, i.e. 0 will be persisted for the post_id column. Due to the primary key constraint, you cannot have a second row with the same key, resulting in the described failure.
There are several strategies available to generate id:
GenerationType.AUTO
GenerationType.SEQUENCE
GenerationType.IDENTITY
GenerationType.TABLE
If you want the primary key values to be auto-generated, use GenerationType.AUTO, it works with MySQL.
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.
We have a Spring Boot/Data-JPA (1.3.3.RELEASE) application using Hibernate implementation where a CSV file is read and inserted into a database table called FIRE_CSV_UPLOAD. For records that are already present we just update them.
We retrieve record ID by querying for unique key (a combination of three columns) but this approach is inefficient for thousands of record in CSV file.
My question is how to update record without querying the table for unique key? If I do not query for ID then the record will be inserted instead of update.
I know one way which is to retrieve all records and store their unique key and ID pairs in a Map. Any other suggestions are very much appreciated. The codes are as below,
Note: They are minimized for brevity.
#Entity
#Table(name = "FIRE_CSV_UPLOAD",
uniqueConstraints={#UniqueConstraint(columnNames = {"account_number" , "account_type", "bank_client_id"})})
public class FireCsv {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#NotNull
#Column(name="account_number")
private String accountNumber;
#NotNull
#Column(name="account_type")
private String accountType;
#NotNull
#Column(name="bank_client_id")
private String bankClientIdNumber;
...
other fields/getters/setters
}
--
public interface FireCsvRepository extends JpaRepository<FireCsv, Long> {
#Query("select u from FireCsv u where u.accountNumber = :accountNumber and u.accountType = :accountType and u.bankClientIdNumber = :bankClientIdNumber ")
FireCsv findRecord(#Param("accountNumber") String accountNumber,
#Param("accountType") String accountType,
#Param("bankClientIdNumber") String bankClientIdNumber);
}
--
#Service
public class FireCsvServiceImpl implements FireCsvService {
other fields/methods
...
#Override
#Transactional
public FireCsv save(final FireCsv fireCsv) {
FireCsv existingFireCsv = fireCsvRepository.findRecord(fireCsv.getAccountNumber(), fireCsv.getAccountType(), fireCsv.getBankClientIdNumber());
// If record exist then mark as update, if not as insert
if (existingFireCsv != null) {
fireCsv.setId(existingFireCsv.getId());
fireCsv.setRecordStatus(CSVUploadRecordStatus.CSV_UPDATE.getStatus());
}
else {
fireCsv.setRecordStatus(CSVUploadRecordStatus.CSV_INSERT.getStatus());
}
fireCsv.setRecordStatusDate(new java.sql.Timestamp(new Date().getTime()));
return fireCsvRepository.save(fireCsv);
}
}
You have to read before deciding to make an update or insert, I dont think there is a way around it.
To make that faster you should add an index to your database
using the three columns "account_number", "account_type", "bank_client_id".
Alternatively you can try to use an composite id using #IdClass as shown in
tutorial-jpa-composite-primary-key
The JPA provider should than automatically create the index for it.
I use Liquibase to create the DDL schema of the database(Derby). Also, I use JPA and EclipseLink and I want to make EclipseLink so that it does not insert any values for primary keys and I want them to be generated through the pure sql. Now, I`ve tried to remove the generation type strategy in the entities, but it is trying to insert null values for PKs to the tables, which are not allowed for PKs.
I`ll be glad if you help me.
Now I have this, but it gives me the exception below.
#Entity
#XmlRootElement
#Table(name = "ROLE")
public class Role implements Serializable {
private static final long serialVersionUID = 4736444799522006644L;
# Id
# JsonIgnore
# GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
AND
CREATE TABLE Role (
id BIGINT PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
role VARCHAR(255)
);
Exception Description: The attribute [id] of class [...] is mapped to a primary key column in the database. Updates are not allowed.
You could create a sequence for generating IDs (instead of this GENERATED BY DEFAULT START WITH 1, INCREMENT BY 1) . Its' a better approach and after that you can use your sequence in JPA:
...
#Entity
public class Employee {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="EMP_SEQ")
#SequenceGenerator(name="EMP_SEQ", sequenceName="EMP_SEQ", allocationSize=100)
private long id;
...
}
You can use sequence in SQL <sequence>.NEXTVAL to get new ID value.
For more info - please check https://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing
Unfortunately this method won't work if your DB does not support sequences.
Or for your Derby DB you can try this (if primary key is generated as identity)
#Entity
public class EntityWithIdentityId {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
....
}
I've a question about JPA and Inheritance (EclipseLink) :
For a school project, I've created an abstract class : HumanEntity
and different subclasses which implement it.
The abstract class:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class HumanEntity implements IHuman{
#Id
#GeneratedValue
protected long id;
#Column(name = "FIRST_NAME")
protected String firstName;
#Column(name = "LAST_NAME")
protected String lastName;
protected LocalDate birthDate;
protected HumanEntity(){
}
#Override
public String getFirstName() {
return firstName;
}
#Override
public String getLastName() {
return lastName;
}
#Override
public LocalDate getBirthDate() {
return birthDate;
}
}
A subclass example :
#Entity
#Table(name="ACTOR")
public class ActorEntity extends HumanEntity{
protected ActorEntity(){}
protected ActorEntity(ActorBuilder actorBuilder){
this.firstName = actorBuilder.getFirstName();
this.lastName = actorBuilder.getLastName();
this.birthDate = actorBuilder.getBirthDate();
}
}
When I run the the project :
-> Ids are shared for every subclasses then I have
In the Actor Table :
1 -> ....
3 -> ....
In the director table :
2 -> ....
How can I create a different Id for every subclasses but with a protected (shared) Id in my code ?
Another little question EclipseLink ,or JPA always create a sequence table but i dont know what is it used for ? can I delete it ?
Thanks a lot !
All objects that are of type HumanEntity or subclasses are instances of HumanEntity, hence the id's need to be consistent, and hence from the same "group" of ids.
You CANNOT have say id 1 for an Actor and id 1 for a Director since they are both HumanEntity, and you need to uniquely identify a HumanEntity. i.e Actor#1 is also HumanEntity#1. So you cannot also have Director#1 since that would also be HumanEntity#1!
If you want to have id's like those then you would need to change your inheritance structure.
First of all it would be interesting which database platform you are using. As you mentioned a school project I assume it is either PostgreSQL or MySQL?!
The ID's values are based on the strategy (see GenerationType) that is used along with the #GeneratedValue annotation. If you don't specify any specific strategy for a #GeneratedValue-field, the default strategy should be javax.persistence.GenerationType.AUTO according to the JPA JavaDoc of GeneratedValue. However EclipseLink seems to use javax.persistence.GenerationType.TABLE according to their documentation:
GenerationType:
The strategy of the GeneratedValue is defined by the GenerationType enumerated type. The strategy defines how the id value should be generated on the database. By default, EclipseLink chooses the TABLE strategy using a table named SEQUENCE, with SEQ_NAME and SEQ_COUNT columns, with allocationSize of 50 and pkColumnValue of SEQ_GEN. If SEQUENCE is used the default database sequence used is named SEQ_GEN_SEQUENCE with an allocationSize of 50.
This basically means that EclipseLink will use a non business database table SEQUENCE to lookup the next values. Furthermore it will use the SEQ_GEN row of this table for generation of all default generated values. That is all entities will share a common pool of generated values by default. I don't have a DMBS at hand right now but looking into this table will probably give you more insights on the used values.
If you want to use a different strategy or another table for generation you should take a look at the JavaDoc of TableGenerator and SequenceGenerator. That way you could create a separate sequence for each subclass.
I think the above already answered your question regarding the sequence table (at least I hope so). So I'd rather not delete it...