Null or Zero Primary Key in unit work clone sqlite - java

Hello I have a Problem with JPA and SQLite.
I have a table called Category in SQLite. This is the Structur of this table:
Category:
catID INTEGER Primary Key
catName TEXT
Now i'm trying to insert a new Category via JPA.
This is my method to do this:
public void insertCategory(EntityManager em, String catName) {
Category cat = new Category();
cat.setCatName(catName);
em.getTransaction().begin();
em.persist(cat);
em.flush();
em.getTransaction().commit();
}
Now I have an Execption at em.flush().
The Exception says
Null or zero primary key encountered in UnitOfWork clone
I know this happens because of the Object cat. The catID is still null.
The class looks like:
#Entity
#Table(name="Category")
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name="catName")
private String catName;
#Id
#Column(name="catID")
private Integer catID;
+ Getter and Setter
}
SQLite increments the Primary Key automatically when not given in the query. How can i inform my Program about the Primary Key to run without getting this Exception??

Thank you Mick Mnemonic for your help. The last answer you give helped me.
I had to generate my database new and add the autoincrement Argument to my Primary Keys. Then the sqlite_sequence Table was generated. Now i can insert Entitys in my database via JPA without getting any Exception.
This is my Generator Annotation:
#GeneratedValue(generator="sqlite")
#TableGenerator(name="sqlite", table="sqlite_sequence",
pkColumnName = "name", valueColumnName = "seq",
pkColumnValue = "Section", initialValue = 1,
allocationSize = 1)
the last two Arguments are necessary because jpa seems to add +50 to the value in the sqlite_seq table and SQLite begins the Autoincrement value at 1.

Related

How to update records in existing column using Spring JPA?

I am new to Java and spring. I have an existing database and table. I just wanted to update the records in the existing table. I have created a class and marked it with #entity annotation and I have spring.jpa.hibernate.auto-ddl set to update in application.properties.
But when I run my program it is creating new columns in the database. I don't want new columns to be added. Just wanted to map the table to the class and update existing records in the table.
Also, my table has 4 columns of which one has not null constraint on it. So when I run the program it's giving me an error saying "ALTER TABLE only allows columns to be added that can contain nulls, or have a DEFAULT definition specified, or a column being added is an identity, or timestamp column or alternatively if none of the previous conditions are satisfied the table must be empty to allow the addition of this column. Column brand_id cannot be added to a not empty table TableName because it does not satisfy these conditions." I could see on the console that it's executing alter table add column command.
Column names in the table are brandId, advanceDescription, and Aliases.
#Entity
Public class TableName {
#Id
private int recid;
private int brandId;
private String advanceDescription;
private String aliases;
}
And the newly added column name in the table is advance_description.
If I understand correctly, you don't want to change the schema of the table. Just add new data to it. To do so you need to disable auto-ddl and set it to none:
spring.jpa.hibernate.auto-ddl=none
Now the second problem is mismatched column names. Try changing your entity definition by setting a matching column name for each field. You can do it by using #Column(name="columnName") annotation. With the provided data it should be something like this:
#Entity
public class TableName {
#Id
private int recid;
#Column(name="brandId")
private int brandId;
#Column(name="advance_description")
private String advanceDescription;
#Column(name="Aliases")
private String aliases;
}
Please try show create table tableName command on db.
it will give you the schema of your table like
CREATE TABLE `tableName` (
`recid` int(11),
`brand_id` int(11) unsigned NOT NULL,
`advance_description` text,
`aliases` text,
PRIMARY KEY (`recid`),
)
#Entity
#Table("tableName")
public class TableName {
#Id
private int recid;
#Column(name="brand_id")
private int brandId;
#Column(name="advance_description")
private String advanceDescription;
#Column(name="aliases")
private String aliases;
}
i.e. now you can see from the db command you will able to see that your table have brand_id field so you want to map brand_id field in your TableName.class with brandId field so in that case you have to add #Column(name = "brand_id) onto your brandId field i.e. in db field is brand_id but in java I want to map brand_id field to brandId.
This applicable to all column. Check for rec_id,advance_description and aliases too.

How to map a table with composite key with one of the column being foreign key on Hibernate

I am having a hard time mapping the following database schema on Hibernate:
The tb_order is an existing table and it is already mapped on Java class. The tb_external_order_details table was recently created and it only have two columns. Both columns are part of the composite Primary Key. The tb_order_id column references to the id column on tb_order table and the external_order_id is just a loose id that doesn't references any column on database.
Please note that the database table and column names are not exactly equal to the Java classes and attributes names. e.g. the java class name is Order and the table name is tb_order. I just think this is important to note because the table/column names inferred by Hibernate may not match (also the table column is snake_case while properties on java classes will be camelCase).
I tried many solutions found here on stack overflow and none of them worked. Also, on the examples I found, no column of the composite Primary Key is a Foreign Key referencing another column of another table.
Try this:
#Entity
public class Order {
#Id
Integer id;
String type;
#OneToMany(mappedBy = "order")
Set<ExternalOrderDetails> externalOrderDetails;
}
#Entity
public class ExternalOrderDetails {
#EmbeddedId
ExternalOrderDetailsId id;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "tb_order_id", insertable = false, updatable = false)
Order order;
}
#Entity
public class ExternalOrderDetailsId {
#Column(name = "tb_order_id")
Integer orderId;
#Column(name = "external_order_id")
Integer externalOrderId;
}

Unique key auto generated in Spring Boot Entity

I have a primary key in my entity table which is autogenerated but now I want unique keys to be auto generated so how to do it
Please help me out.
#Entity
#Table(name = "director")
public class Director {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
//how to make this field auto generated like above one
#Column(name = "subid", unique=true)
private long sub_id;
My database table picture is here please refer
You can use timestamp or static AtomicLong counter as sub_id value. Try to define method with annotation #PrePersist in your entity class and your JPA provider will execute it before persisting an object.
Note: using timestamp in concurrent environment may lead to collisions and values won't be unique.
private final static AtomicLong subIdCounter = new AtomicLong(System.nanoTime());
#PrePersist
void sub_id() {
this.sub_id = subIdCounter.incrementAndGet();
}
After a short study it seems that that Hibernate supports the feature of generated values only with fields annotated with #Id. With #Id and default #generatedValue Hibernate creates - depending on the database and dialect used - appropriate way to generate the value of id field. usually this is something like creating a sequence and setting the column definition like (examples are from Postgres 12):
id bigint not null nextval('director_id_seq'::regclass)
Interesting thing is that this is done by issuing create statement like this:
create table director (id bigserial not null, primary key (id))
So, the column type bigserial actually generates sequence that is used to insert default value to the id column.
There are two options it you want to generate the value for column sub_id as it is generated to the column id. Both are database dependent.
Just create the sequence manually to the database and alter column sub_id to fetch the default value from the sequence.
OR
Change your column definition to use appropriate column type, like:
#Column(name = "subid", insertable = false,
nullable = false, unique = true, columnDefinition = "bigserial")
private long sub_id;
This will cause Hibernate to generate table like:
create table director (id bigserial not null, subid bigserial not null, primary key (id))
and result to a column like:
subid bigint not null nextval('director_subid_seq'::regclass)
But again: this is database specific stuff.
Also note: that JPA is aware only of the value that is stored to the id field. The subid is inserted to the database table but the sub_id field is not populated until entity is refreshed in its persistence context.

How to insert test data into H2 using sequence for default value, when Hibernate entity has GenerationType.SEQUENCE

When an entity is mapped with GenerationType.IDENTITY an sql insert into a h2 test db will have a default value for the id column pointing to a sequence. When mapped with GenerationType.SEQUENCE it won't have a default value.
We want to use GenerationType.SEQUENCE as it's more verbose and we want to show where everything comes from. During testing we've stumbled upon this problem - when using #Sql to insert a script into an embedded h2 database we get an error about a not null constraint.It also happens when we just execute the script as a native query.
This doesn't happen when using GenerationType.IDENTITY. The same script from above works properly.
If we use GenerationType.SEQUENCE and pass it the id by calling a next value function like this:
INSERT INTO example (id, name) (NEXTVAL('example_id_seq'), 'test');
the script will work without a problem.
Here is an example mapping an insert where the problem occurs:
#Entity
#Table(schema = DbSchemas.PUBLIC, name = DbTables.Example)
public class Example implements Serializable {
private static final long serialVersionUID = -8827852720381659873L;
private static final String EXAMPLE_GENERATOR = "example_generator";
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = EXAMPLE_GENERATOR)
#SequenceGenerator(name = EXAMPLE_GENERATOR, sequenceName = DbSequences.EXAMPLE_ID_SEQUENCE, allocationSize = 1)
private Integer id;
#Column(name = "name", nullable = false)
private String name;
}
INSERT INTO public.example (name)
VALUES ('test');
o.h.engine.jdbc.spi.SqlExceptionHelper : NULL not allowed for column "ID"; SQL statement:
I'd like to be able to call the insert shown above for entities mapped with GenerationType.SEQUENCE. The insert must use and increment the sequence described in the mapping.

JPA Mapping Multi-Rows with ElementCollection

I'm trying to follow the JPA tutorial and using ElementCollection to record employee phone numbers:
PHONE (table)
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
Short version
What I need is a class like this:
#Entity
#Table(name="Phones")
public class PhoneId {
#Id
#Column(name="owner_id")
long owner_id;
#Embedded
List<Phone> phones;
}
that stores each person's phone numbers in a collection.
Long version
I follow the tutorial code:
#Entity
#Table(name="Phones")
public class PhoneId {
#Id
#Column(name="owner_id")
long owner_id;
#ElementCollection
#CollectionTable(
name="Phones",
joinColumns=#JoinColumn(name="owner_id")
)
List<Phone> phones = new ArrayList<Phone>();
}
#Embeddable
class Phone {
#Column(name="type")
String type = "";
#Column(name="number")
String number = "";
public Phone () {}
public Phone (String type, String number)
{ this.type = type; this.number = number; }
}
with a slight difference that I only keep one table. I tried to use the following code to add records to this table:
public static void main (String[] args) {
EntityManagerFactory entityFactory =
Persistence.createEntityManagerFactory("Tutorial");
EntityManager entityManager = entityFactory.createEntityManager();
// Create new entity
entityManager.getTransaction().begin();
Phone ph = new Phone("home", "001-010-0100");
PhoneId phid = new PhoneId();
phid.phones.add(ph);
entityManager.persist(phid);
entityManager.getTransaction().commit();
entityManager.close();
}
but it keeps throwing exceptions
Internal Exception: org.postgresql.util.PSQLException: ERROR: null
value in column "type" violates not-null constraint Detail: Failing
row contains (0, null, null). Error Code: 0 Call: INSERT INTO Phones
(owner_id) VALUES (?) bind => [1 parameter bound] Query:
InsertObjectQuery(tutorial.Phone1#162e295)
What did I do wrong?
Sadly, i think the slight difference that you only keep one table is the problem here.
Look at the declaration of the PhoneId class (which i would suggest is better called PhoneOwner or something like that):
#Entity
#Table(name="Phones")
public class PhoneId {
When you declare that a class is an entity mapped to a certain table, you are making a set of assertions, of which two are particularly important here. Firstly, that there is one row in the table for each instance of the entity, and vice versa. Secondly, that there is one column in the table for each scalar field of the entity, and vice versa. Both of these are at the heart of the idea of object-relational mapping.
However, in your schema, neither of these assertions hold. In the data you gave:
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
There are two rows corresponding to the entity with owner_id 1, violating the first assertion. There are columns TYPE and NUMBER which are not mapped to fields in the entity, violating the second assertion.
(To be clear, there is nothing wrong with your declaration of the Phone class or the phones field - just the PhoneId entity)
As a result, when your JPA provider tries to insert an instance of PhoneId into the database, it runs into trouble. Because there are no mappings for the TYPE and NUMBER columns in PhoneId, when it generates the SQL for the insert, it does not include values for them. This is why you get the error you see - the provider writes INSERT INTO Phones (owner_id) VALUES (?), which PostgreSQL treats as INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null), which is rejected.
Even if you did manage to insert a row into this table, you would then run into trouble on retrieving an object from it. Say you asked for the instance of PhoneId with owner_id 1. The provider would write SQL amounting to select * from Phones where owner_id = 1, and it would expect that to find exactly one row, which it can map to an object. But it will find two rows!
The solution, i'm afraid, is to use two tables, one for PhoneId, and one for Phone. The table for PhoneId will be trivially simple, but it is necessary for the correct operation of the JPA machinery.
Assuming you rename PhoneId to PhoneOwner, the tables need to look like:
create table PhoneOwner (
owner_id integer primary key
)
create table Phone (
owner_id integer not null references PhoneOwner,
type varchar(255) not null,
number varchar(255) not null,
primary key (owner_id, number)
)
(I've made (owner_id, number) the primary key for Phone, on the assumption that one owner might have more than one number of a given type, but will never have one number recorded under two types. You might prefer (owner_id, type) if that better reflects your domain.)
The entities are then:
#Entity
#Table(name="PhoneOwner")
public class PhoneOwner {
#Id
#Column(name="owner_id")
long id;
#ElementCollection
#CollectionTable(name = "Phone", joinColumns = #JoinColumn(name = "owner_id"))
List<Phone> phones = new ArrayList<Phone>();
}
#Embeddable
class Phone {
#Column(name="type", nullable = false)
String type;
#Column(name="number", nullable = false)
String number;
}
Now, if you really don't want to introduce a table for the PhoneOwner, then you might be able to get out of it using a view. Like this:
create view PhoneOwner as select distinct owner_id from Phone;
As far as the JPA provider can tell, this is a table, and it will support the queries it needs to do to read data.
However, it won't support inserts. If you ever needed to add a phone for an owner who is not currently in the database, you would need to go round the back and insert a row directly into Phone. Not very nice.

Categories