Question
Why I get NULL not allowed for column "ID" exception when I execute INSERT INTO PUBLIC.MY_ENTITY (name) VALUES ('test name');?
Setup
I'm using Spring Boot and Hibernate. Spring Boot is launched with properties:
hibernate.hbm2ddl.auto=update
spring.jpa.hibernate.ddl-auto=update
I have entity:
#Entity
#Table(name = "MY_ENTITY")
public class MyEntity {
#Id
#SequenceGenerator(sequenceName = "MY_ENTITY_SEQ", name = "MyEntitySeq")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MyEntitySeq")
private Long id;
#Column(unique = true, nullable = false)
private String name;
// getters & setters
// ...
}
Table has been generated on application start.
I can prove that sequence has been created with the next query:
SELECT * FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_NAME = 'MY_ENTITY_SEQ'
P.S.
For some reason Hibernate does not link sequence to id auto generation. I can solve the problem with the query below. But how make Hibernate generate the query below?
ALTER TABLE PUBLIC.MY_ENTITY ALTER COLUMN ID BIGINT DEFAULT (NEXT VALUE FOR PUBLIC.MY_ENTITY_SEQ) NOT NULL NULL_TO_DEFAULT SEQUENCE PUBLIC.MY_ENTITY_SEQ;
INSERT INTO PUBLIC.MY_ENTITY (name) VALUES ('test name');
Give the #SequenceGenerator an allocationSize: #SequenceGenerator(sequenceName = "MY_ENTITY_SEQ", name = "MyEntitySeq", allocationSize=1)
Check the dialect you are using
Set "hibernate.id.new_generator_mappings" to "true"
Related
My project is using .NET Core 3.1 and I have my stored procedures executing in my repository class. I want to insert and return the scope identity(the id of the record that just inserted UserNumber) so I can use it for another stored proc within this same method. The problem I have here is that parameters[1].Value value is returning zero.
Here is an abbreviation of my stored proc:
ALTER PROCEDURE [dbo].[InsertUser]
#iUserNumber int OUTPUT,
As
INSERT dbo.tblUser (
CreatedBy
)
VALUES (#LoginUserId)
IF ##ERROR <> 0 GOTO ERRHANDLER
SET #UserNumber = SCOPE_IDENTITY() /** this is the primary Key **/
RETURN(#UserNumber)
Here is a sample of my repository
public int InsertUsers(int LoginUserId, int UserNumber)
{
SqlParameter[] parameters = new List<SqlParameter>()
{
_dbContext.CreateSqlParameter(StoredProcedureConstants.LoginUserId,SqlDbType.Int,LoginUserId.ToSafeInt()),
_dbContext.CreateSqlParameter(StoredProcedureConstants.UserNumber,SqlDbType.Int,UserNumber.ToSafeInt())
}.ToArray();
var intResult = _dbContext.ExecuteNonQuery(StoredProcedureConstants.InsertUsers, parameters);
var result2 = parameters[1].Value; //This comes back as zero
How do I assign the scope identity to result2?
Should be something like:
CREATE OR ALTER PROCEDURE [dbo].[InsertUser]
#LoginUserId int,
#iUserNumber int OUTPUT
As
INSERT dbo.tblUser (CreatedBy)
VALUES (#LoginUserId)
SET #iUserNumber = SCOPE_IDENTITY() /** this is the primary Key **/
and
SqlParameter[] parameters = new List<SqlParameter>()
{
_dbContext.CreateSqlParameter("#LoginuserId",SqlDbType.Int,LoginUserId),
_dbContext.CreateSqlParameter("#iUserNumber",SqlDbType.Int)
}.ToArray();
parameters[1].Direction = ParameterDirection.Output;
_dbContext.ExecuteNonQuery(StoredProcedureConstants.InsertUsers, parameters);
var result2 = parameters[1].Value;
Environment: JPA 2.1, EclipseLink 2.6.3, SQL Server 2016
I want to use a field of type Timestamp for versioning and optimistic. I do not have option to use numeric column for versioning. My understanding is I just need to annotate the field with #Version and that all.
Database Table: token_t
token_id int PK
token_name varchar(100)
last_updt_dtm datetime
Entity Class
#Entity
#Table(name = "token_t")
public class TokenAE {
#Id
#Column(name = "token_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int tokenId;
#Column(name = "token_name")
private String tokenName;
#Version
#Column(name = "last_updt_dtm")
private Timestamp lastUpdtDtm;
// getter/setter omitted to avoid cluttering
}
Test Method
#Test
public void optimisticLockingTest1() throws Exception {
PersistenceHelper.getEntityManager().getTransaction().begin();
TokenAE tokenAE = tokenDAO.getToken(616);
assertNotNull("tokenAE is null", tokenAE);
tokenAE.setTokenName("new token name");
PersistenceHelper.getEntityManager().merge(tokenAE);
PersistenceHelper.getEntityManager().getTransaction().commit();
}
Note - PersistenceHelper is just helper class instantiating entity manager
As you can see, I am loading TokenAE updating name and doing merge. I made sure that underlying database record is not changed. So I am expecting the merge/update should be successful but it always throws OptimisticLockException.
See the stacktrace below. I enabled JPA query/param logging and I can see the UPDATE query and bind parameters. The value of last_updt_dtm in WHERE clause [2018-07-17 22:59:48.847] matches exactly to the value in database record and this UPDATE query should return rowCount 1 and it should be successful.
I have no idea what going on here. Any help is greatly appreciated.
Exception Stacktrace
[EL Fine]: sql: 2018-07-18 23:54:13.137--ClientSession(1451516720)--Connection(1323996324)--Thread(Thread[main,5,main])--
UPDATE token_t SET token_name = ?, last_updt_dtm = ? WHERE ((token_id = ?) AND (last_updt_dtm = ?))
bind => [new token name, 2018-07-18 23:54:13.35, 616, 2018-07-17 22:59:48.847]
[EL Warning]: 2018-07-18 23:54:13.286--UnitOfWork(998015174)--Thread(Thread[main,5,main])--Local Exception Stack:
Exception [EclipseLink-5006] (Eclipse Persistence Services - 2.6.3.v20160428-59c81c5): org.eclipse.persistence.exceptions.OptimisticLockException
Exception Description: The object [TokenAE [tokenId=616, tokenName=new token name, lastUpdtDtm=2018-07-18 23:54:13.35]] cannot be updated because it has changed or been deleted since it was last read.
Class> com.test.TokenAE Primary Key> 616
at org.eclipse.persistence.exceptions.OptimisticLockException.objectChangedSinceLastReadWhenUpdating(OptimisticLockException.java:144)
at org.eclipse.persistence.descriptors.VersionLockingPolicy.validateUpdate(VersionLockingPolicy.java:790)
at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.updateObjectForWriteWithChangeSet(DatabaseQueryMechanism.java:1086)
at org.eclipse.persistence.queries.UpdateObjectQuery.executeCommitWithChangeSet(UpdateObjectQuery.java:84)
at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.executeWriteWithChangeSet(DatabaseQueryMechanism.java:301)
at org.eclipse.persistence.queries.WriteObjectQuery.executeDatabaseQuery(WriteObjectQuery.java:58)
at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:904)
at org.eclipse.persistence.queries.DatabaseQuery.executeInUnitOfWork(DatabaseQuery.java:803)
at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWorkObjectLevelModifyQuery(ObjectLevelModifyQuery.java:108)
at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWork(ObjectLevelModifyQuery.java:85)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2896)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1857)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1839)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1790)
at org.eclipse.persistence.internal.sessions.CommitManager.commitChangedObjectsForClassWithChangeSet(CommitManager.java:273)
at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsWithChangeSet(CommitManager.java:131)
at org.eclipse.persistence.internal.sessions.AbstractSession.writeAllObjectsWithChangeSet(AbstractSession.java:4264)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1441)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1531)
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitRootUnitOfWork(RepeatableWriteUnitOfWork.java:278)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commit(UnitOfWorkImpl.java:1113)
at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:137)
at sunlife.us.dc.bds.token.domain.TokenDAOTest.optimisticLockingTest1(TokenDAOTest.java:39)
I am having trouble to understand how properly persist entities with sub-entities when the JVM has been restarted and the database already contains data from previous sessions.
I have roughly the following entities:
#Entity
public class Organization {
...
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "\"ADDRESS_ID\"", nullable = false)
private Address address;
}
#Entity
public class Address {
...
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "\"ADDRESS_ID\"")
private int addressId;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.MERGE, optional = false)
#JoinColumn(name = "\"ADDRESS_TYPE_ID\"", nullable = false)
private AddressType addressType;
}
#Entity
public class AddressType {
...
// Not bi-directional, so nothing special here
}
It is excpected that the address types are present in the database (CascadeType.MERGE) before creating an address. A new organization is created with a new address and the address has a type set from the given selection. => This works ok when there is a clean database (only address types present).
Still developing, so every now and then I do shutdown the server (JVM) and restarted the application. Then I want to add a new organization to database which already contains data persisted in previous sessions, then I get the following error:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: The statement was aborted because it would have caused a duplicate key value in a unique or primary key constraint or unique index identified by 'SQL151120084237691' defined on 'ADDRESS'.
Error Code: -20001
Call: INSERT INTO "ADDRESS" ("ADDRESS_ID", "STREET_ADDRESS", "COUNTRY", "ZIP_CODE", "CITY", "ADDRESS_TYPE_ID") VALUES (?, ?, ?, ?, ?, ?)
bind => [2, testroad 1, Country, 99999, testcity, ABCDEF-123456]
It tries to use the same ID as already exists in the database. How do I make it realize that the id is already used and it should continue from last?
Notes:
- The address is persisted as part of the organization (CascadeType.ALL) not separately.
- In tests, I am loading all the existing organiztations to the same EntityManager that does the persisting operation => The organization has its addresses accessed eagerly, so they should be available in the em-cache. The duplicate address_id it complains about in unit tests seems to be an orphan entity (maybe this is the reason of the error actually?).
- I can get this error in unit tests using Derby, but a test server using Oracle DB has these same errors in log.
- I also tried adding a 'find all' query to load all address-entities into the cache of the same EntityManager that does the persisting operation of organization. The 'find all' is executed is before the persisting is done => it still failed.
// UPDATE
This same thing happens even that I use TableGenerator to get the id values.
#Entity
public class Address {
...
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "addr_gen")
#TableGenerator(name = "addr_gen", allocationSize = 1, initialValue = 100, table = "\"ADDRESS_GEN\"")
#Column(name = "\"ADDRESS_ID\"")
private int osoiteId;
...
}
The generator table gets created, but it remains empty. The id's however start running from the initial value of '100'.
Some more notes:
- When using self defined table and inserting a value there for the sequence, the id for address-entities continues correctly from that value. When the test is finsihed, the table gets emptied while there still remains data in the tables => Will fail next time.
- When using GenerationType.AUTO, the sequence table gets a default sequence, but after tests it is cleared (same thing as with self defined table)
^I guess this has happened in test servers and it can be duplicated by not emptying the database after test. However the sequence table gets emptied. So the question would be, how to synchronize the sequence table after JVM boot (or prevent it from not emptying itself)?
I do not know if this a good solution or even right in general for original topic, but I managed to make some kind of workaround by defining the sequences separately for all auto-generated id fields.
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "addrSeq")
#SequenceGenerator(name = "addrSeq", sequenceName = "addr_seq", allocationSize = 10)
#Column(name = "\"ADDRESS_ID\"")
private int addressId;
It seems to work, though I do not know why this behaves essentially differently than using 'AUTO'?
Is it normal that the default sequence is nulled when the server is restarted?
I am using DbUnit to run some test on a postgreSql database. In order to be able to run my test, I bring the database into a well known state by repopulating the database tables before each test, running a clean insert. Therefore I use the FlatXmlDataSet definition below (compare with the attached SQL schema).
However, if I run the testCreateAvatar() test case, I get an exception because of a status code mismatch, which is caused by a failed sql insert, because of an already existing primary key (id field). A look into my database shows me, that the insert of the test datasets does not update the corresponding *avatars_id_seq* and *users_id_seq* sequence tables, which are used to generate the id fields (mechanism of postgresql to generate auto-increment values).
That means, that the auto-increment value is not updated, if I define static IDs in the FlatXmlDataSet definitions. So my question is how I could change this behavior or set the auto-increment value on my own (using DbUnit).
Avatar creation test case
#Test
public void testCreateAvatar() throws Exception {
// Set up the request url.
final HttpPost request = new HttpPost(
"http://localhost:9095/rest/avatars");
// Setup the JSON blob, ...
JSONObject jsonAvatar = new JSONObject();
jsonAvatar.put("imageUrl", "images/dussel.jpg");
// ... add it to the post request ...
StringEntity input = new StringEntity(jsonAvatar.toString());
input.setContentType("application/json");
request.setEntity(input);
// ... and execute the request.
final HttpResponse response = HttpClientBuilder.create().build()
.execute(request);
// Verify the result.
assertThat(response.getStatusLine().getStatusCode(),
equalTo(HttpStatus.SC_CREATED));
// Fetch dussel duck from the database ...
Avatar dussel = getServiceObjDao().queryForFirst(
getServiceObjDao().queryBuilder().where()
.eq("image_url", "images/dussel.jpg")
.prepare());
// ... and verify that the object was created correctly.
assertThat(dussel, notNullValue());
assertThat("images/dussel.jpg", equalTo(dussel.getImageUrl()));
}
The DbUnit dataset
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<!-- Avatars -->
<avatars
id="1"
image_url="images/donald.jpg" />
<avatars
id="2"
image_url="images/daisy.jpg" />
<!-- Users -->
<users
id = "1"
name = "Donald Duck"
email = "donald.duck#entenhausen.de"
password = "quack" />
<users
id = "2"
name = "Daisy Duck"
email = "daisy.duck#entenhausen.de"
password = "flower" />
</dataset>
The users and avatars table schema
CREATE TABLE avatars (
id BIGSERIAL PRIMARY KEY,
cdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
mdate TIMESTAMP,
image_url VARCHAR(200),
UNIQUE (image_url)
);
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
cdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
mdate TIMESTAMP,
name VARCHAR(160) NOT NULL,
email VARCHAR (355) UNIQUE NOT NULL,
password VARCHAR(30) NOT NULL,
avatar_id BIGINT,
UNIQUE (name),
CONSTRAINT user_avatar_id FOREIGN KEY (avatar_id)
REFERENCES avatars (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
The function below finds all sequences in a database, extracts the name of the corresponding table from the sequence name and finally updates the current value of the sequences based on the maximum id value in the corresponding table. As there has been no better solution yet, this seems to be the way to go. Hope, this helps someone.
Simple solution based on harmic's suggestion
#Before
public void resetSequence() {
Connection conn = null;
try {
// Establish a database connection.
conn = DriverManager.getConnection(
this.props.getProperty("database.jdbc.connectionURL"),
this.props.getProperty("database.jdbc.username"),
this.props.getProperty("database.jdbc.password"));
// Select all sequence names ...
Statement seqStmt = conn.createStatement();
ResultSet rs = seqStmt.executeQuery("SELECT c.relname FROM pg_class c WHERE c.relkind = 'S';");
// ... and update the sequence to match max(id)+1.
while (rs.next()) {
String sequence = rs.getString("relname");
String table = sequence.substring(0, sequence.length()-7);
Statement updStmt = conn.createStatement();
updStmt.executeQuery("SELECT SETVAL('" + sequence + "', (SELECT MAX(id)+1 FROM '" + table + "'));");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
conn.close();
} catch (SQLException e) {
}
}
}
You can set the value of a sequence using setval, for example
SELECT SETVAL('sequence_name', 1000);
Where sequence_name is the name of the sequence, visible in psql using /dt on the table, and 1000 is the value you want to set it to. You would probably want to set it to the Max value of Id in the table.
What I don't really know is how to get DbUnit to emit this SQL.
I have a below mapping
#Entity
#Table(name = "auctions")
public class Auction{
.
.
#OneToMany(cascade = CascadeType.ALL, mappedBy = "auction")
private List<AuctionParamValue> auctionParamValueList;
.
.
}
#Entity
#Table(name = "auction_param_values")
public class AuctionParamValue {
#EmbeddedId
protected AuctionParamValuePK auctionParamValuePK;
#JoinColumn(name = "auction_param_id", referencedColumnName = "auction_param_id",updatable=false,insertable=false)
#ManyToOne #MapsId("auctionParamId")
private AuctionParam auctionParam;
#JoinColumn(name = "auction_id", referencedColumnName = "auction_id",updatable=false,insertable=false)
#ManyToOne #MapsId("auctionId")
private Auction auction;
}
#Embeddable
public class AuctionParamValuePK {
#Id
#Basic(optional = false)
#Column(name = "auction_id")
#Nullable
private Long auctionId = null;
#Id
#Basic(optional = false)
#Column(name = "auction_param_id")
#Nullable
private Long auctionParamId = null;
}
#Entity
#Table(name = "auction_params")
public class AuctionParam {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "auctionParam")
private List<AuctionTypeParam> auctionTypeParamList;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "auctionParam")
private List<AuctionParamValue> auctionParamValueList;
}
}
When I try to persist auction (I am manually setting the auctionParamId and expecting the auctionId to be automaticlly set (may be the last inserted id) )
but I am getting below error, I am not sure why the auctionId in the query is going as 0 instead of latest id in the auction.(I am using eclipselink jpa provider)
Internal Exception: com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`portaldemo`.`auction_param_values`, CONSTRAINT `auction_param_values_auction_id_fk` FOREIGN KEY (`auction_id`) REFERENCES `auctions` (`auction_id`))
Error Code: 1452
Call: INSERT INTO auction_param_values (auction_param_val, create_ts, last_updt_ts, auction_param_id, auction_id) VALUES (?, ?, ?, ?, ?)
bind => [2011-02-12 04:00:00, 2011-01-27 12:02:00.28, 2011-01-27 12:17:43.25, 2, 0]
Query: InsertObjectQuery(com.eaportal.domain.AuctionParamValue[auctionParamValuePK=com.eaportal.domain.AuctionParamValuePK[auctionId=0, auctionParamId=2]])
Here the [auctionId=0 is always comming as 0 and not the last inserted id :(
What is theproblem with this mapping ?
An #GeneratedValue will only set the value of the attribute it is annotated on, if you have other attributes in other classes that reference the id you are responsible for setting these.
i.e. you would need to first persist and flush the Auction, and then create the AuctionParamValue using its generate Id.
Or, if you used TABLE or SEQUENCE id generation then you would just need to call persist, and not the flush. In general I would never recommend IDENTITY sequencing as its values cannot be preallocated.
But really you should not have the duplicate fields as all. Remove the #EmbeddedId auctionParamValuePK entirely and just add #Id to the two #ManyToOnes, and use an #IdClass instead. This will make things much simplier and will just work, even with IDENTITY id generation.
You could also instead remove the insertable/updateable=false on the two #ManyToOne mappings and instead put them on the #EmbeddedId attributes, this will have the foreign key written from the relationships, but your object will still be corrupt in memory.
See,
http://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Primary_Keys_through_OneToOne_and_ManyToOne_Relationships
You could try two things:
make the two ids nullable: Use wrapper Types instead of primitives (Integer, Long), and set it to null before saving
leave the combinded Primary ID field (auctionParamValuePK) empty (null) when you save it.
I don't know if this fix the problem, but I am sure that you need to do at least one of them to get it working.