I am using hibernate and i mapped my table with my bean. If i am not assign any particular column value as #id, it throws "No identifier specified for entity" error, however it is not primary key in my data table. I want to add multiple records with same data. how can i do it? When i annotated my product Name column with #id my code runs perfect.
Hibernate requires an identifier for each entity. However, it is possible to use native queries to insert new records. And, in the same way, to recover them.
Related
I`ve noticed that in my project some composite constraint are set in a liquidbase,
for example
constraint importer_ukey unique (name, country_id, is_importer, is_manufacturer)
but then they are repeated in entity definition in #Table annotation
#Table(uniqueConstraints = {#UniqueConstraint(name = "importer_ukey",
columnNames = {"name", "country_id", "is_importer", "is_manufacturer"})})
Is there any positive in repetition of uniqueConstraint in entity ?
Other than for creating the schema based on the JPA annotations, it is also a needed at runtime for the JPA provider to order INSERT, UPDATE and DELETE statements correctly.
Unique constraints ensure that the data in a column or combination of columns is unique for each row. A table's primary key, for example, functions as an implicit unique constraint. In JPA, you represent other unique constraints with an array of UniqueConstraint annotations within the table annotation. The unique constraints you define are used during table creation to generate the proper database constraints, and may also be used at runtime to order INSERT, UPDATE , and DELETE statements. For example, suppose there is a unique constraint on the columns of field F. In the same transaction, you remove an object A and persist a new object B, both with the same F value. The JPA runtime must ensure that the SQL deleting A is sent to the database before the SQL inserting B to avoid a unique constraint violation.
Source: https://openjpa.apache.org/builds/1.0.2/apache-openjpa-1.0.2/docs/manual/jpa_overview_mapping_unq.html
So yes: it is important.
Before I added Flyway to my project, I could run POST request and the new user was created successfully with ID = 1, next one ID = 2 etc.
Then I added Flyway to create tables and insert some test data by V1_init.sql:
create table "user"(
id int8 not null,
username varchar(255),
);
insert into "user" values (1, 'user1');
insert into "user" values (2, 'user2');
insert into "user" values (3, 'user3');
Table is created. Users are inserted.
Trying to run POST request -> error 500
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "organisation_pkey" Key (id)=(1) already exists.
So my app should add new user with ID=4 but it looks like it can't recognize that there are 3 users already added.
I'm using GenericEntity:
#Getter
#Setter
#MappedSuperclass
public abstract class GenericEntity<ID extends Serializable> implements Serializable {
#Id
#GeneratedValue
protected ID id;
}
application.properties:
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/my-app
spring.datasource.username=user
spring.datasource.password=user
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.format_sql=true
I tried to use all strategies #GeneratedValue, changing spring.jpa.hibernate.ddl-auto, adding users in init.sql without id (not working)
but still no positive effects. Any ideas what could be wrong?
You seem to have only a half understanding of what you're doing...
I tried to use all strategies #GeneratedValue
You don't need to randomly try strategies, you need to pick the one that matches your current database design.
changing spring.jpa.hibernate.ddl-auto
This is dangerous and you should set it to "none", given that you are using flyway.
adding users in init.sql without id (not working)
This will only work if postgresql is set up to automatically generate ids (which is easiest through a sequence).
From your code, it does not look like that is the case.
what could be wrong?
JPA's #GeneratedValue is capable of ensuring that values are generated when it is responsible for creating rows (that means when you pass EntityManager#persist). It does not and can not know about your flyway scripts where you bypass JPA to insert rows manually.
Furthermore, let's look at #GeneratedValue's strategy property. The strategy you choose will influence how JPA generates IDs. There are only a few options: TABLE, SEQUENCE, IDENTITY and AUTO. Since you did not explicitly specify a strategy, you are currently using the default, which is AUTO. This is not recommended because it is not explicit, and now it's hard to say what your code is doing.
Under the TABLE and SEQUENCE strategies, JPA will do an interaction with the database in order to generate an ID value. In those cases, JPA is responsible for generating the value, though it will rely on the database to do so. Unsurprisingly, the former will use a table (this is rare, btw, but also the only strategy that is guaranteed to work on all RDBMS) and the latter will use a sequence (far more common and supported by practically every commercially relevant RDBMS).
With IDENTITY, JPA will not attempt to generate a key at all, because this strategy assumes that the DB will generate an ID value on its own. The responsibility is thus delegated to the database entirely. This is great for databases that have their own auto-increment mechanism.
Postgres does not really have an auto-increment system but it has some nice syntactic sugar that nearly makes it work like it: the serial "datatype". If you specify the datatype of a column as "serial", it will in fact be created with datatype int, but postgresql will also create a sequence and tie the default value of the ID column to the sequence's next value generator.
In your case, JPA is most likely using either SEQUENCE or TABLE. Since your DDL setting is set to "update", Hibernate will have generated a table or sequence behind your back. You should check your database with something like pgAdmin to verify which it is, but I'd put my money on a sequence (so I'm assuming it's using the SEQUENCE strategy).
Because you haven't specified a #SequenceGenerator, a default will be used which, AFAIK, will start from 1.
Then when JPA tries to insert a new row, it will call that sequence to generate an ID value. It will get the next value of the sequence, which will be 1. This will conflict with the IDs you manually entered in flyway.
My recommended solution would be to:
redefine your postgresql data type from int8 to "serial" (which is actually int + a sequence + sets up default value linking the ID column to the sequence so that postgres will automatically generate an ID if you don't explicitly specify one - careful, also don't specify null, just don't specify the ID column in the insert statement at all!)
explicitly set the generator strategy to IDENTITY on the JPA side
update your flyway scripts to insert users without explicit ID value (this will ensure that the test data advance the sequence, so that when JPA uses that same sequence later, it will not generate a conflicting ID)
I'd say there are alternative solutions, but other than using the TABLE strategy or generating keys in memory (both things which you should avoid), there isn't really a viable alternative because it will boil down to using a sequence anyway. I suppose it's possible to manually specify the sequence, forego the default value on the id field, call the sequence manually in your insert statements, and map the sequence explicitly in JPA... but I don't see why you'd make things hard on yourself.
In the description of the delete method in org.springframework.data.repository,CrudRepository interface it is written only that it deletes a given entity and that it accepts entity object itself.
It specifies nothing about entity's id needing to be unique.
However, this method works only when entity has an unique id. If the table may have multiple rows with the same id, this method fails when entity whose id is not unique is attempted to be deleted (at least in my case).
Now I understand that it is a very bad thing that an id is not unique in the table, but, in theory, it should work since this method accepts the entire entity as a parameter, and every entity (when all its columns are combined) in my table is unique. There are no two identical rows in a table since table has an unique constraint on a combination of all its columns.
Sure, there are other methods, like deleteById which would fail since they only accept id as a parameter and not the whole entity and since id is not unique, Spring does not know which entity to delete. Sure.
But why delete method fails when it should be able to distinguish between entities with the same id since it accepts the whole entitiy as a parameter (thus giving it access to all other columns of the entity and not just the id column)?
The requirement for the JPA id is that it is a primary key i.e. uniquely identifies the row. If you do not follow this, bad things will happen. You can have a composite primary key though, as it seems your row is identified by multiple columns. Look into #Embeddable/#EmbeddedId mappings for this purpose.
Suppose I have EntityManager em and that I have Entity e. When I call em.persist(e) the Java EE JPA documentation says that an EntityExistsException is thrown if e already exists in the persistence context.
Now, suppose that entities of type Entity have a surrogate key generated automatically by the database, but they also have a field (or fields) with uniqueness constraints that make any entity of type Entity unique.
How does EntityManager em determines that the new element I am trying to insert to the database already exists there? Is it from the equals method defined in Entity, or is it by attempting to insert to the database and getting back an error because some uniqueness constraints were violated? (Obviously, I think it shouldn't be because the surrogate primary key of the object I am inserting is equal to the surrogate primary key of an object in the database, since the surrogate primary key of the object I am inserting is null (or my thinking is wrong here?)). If it is the latter, how am I sure that the persist is throwing an EntityExistsException instead of a PersistenceException?
There is no other way than doing an INSERT and catching a database exception. Any other ways are unreliable since databases are multi-user environments.
For example, oracle throws a "unique constraint violated" and it shows which
constraints were violated.
QL Error: ORA-00001: unique constraint (SCHEMA_NAME.CONSTRAINT_NAME) violated
I have a data model in which a number of entities inherit some common attributes from a single superclass entity. I am using InheritanceType.JOINED on the superclass, which causes Hibernate to create a single table for attributes defined in the superclass, with subclass tables containing only columns that are added by the subclass (so to load the attributes for a subclass instance, a join is performed between the two tables). That is all working fine.
What I'd like to do, however, is specify a unique constraint that includes fields in both the subclass and superclass tables. For instance, say that my superclass entity is something like:
Thing: {id, name}
...and then I have some subclass entities like:
Company: {address} //inherits 'id' and 'name' from 'Thing'
Employee: {company} //inherits 'id' and 'name' from 'Thing'
...and I want to configure Hibernate to automatically enforce that a given Company cannot have two Employee's with the same name. The company field is in the Employee table, but the name field is in the Thing table, so is there any way to get Hibernate to enforce this constraint, or do I need to do it programmatically whenever I add a new Employee?
If it's not possible in the Database it won't be possible with Hibernate. You can't create one constraint on multiple tables with SQL so neither in Hibernate.
You could work around this by creating a new Entity holding only the company and employee id and setting a unique constraint on those 2 fields but I would recommend enforcing this programmatically.
You could not use InheritanceType.JOINED, then everything ends up in a huge table, and you could write your constraint. As said before: What you want is just not possible in a relational DB.