I have PostgreSQL database with multiple schema and I'm using Apache Cayenne to generate Java classes. Problem is that cayenne skips foreign keys on tables in different schema. Example:
Table schema_b.booking that references schema_a.my_user:
create table schema_b.booking
(
id bigserial not null constraint booking_pkey primary key,
address_id integer not null constraint cde_fk references schema_b.address
...,
created_by integer not null constraint abc_fk references schema_a.my_user
);
Generated Java class looks like:
class Booking {
private Long id;
private Address addressId; //this is OK
private Integer createdBy; //NOT OK (Integer instead of MyUser)
}
Console log shows this entry for every FK in different schema:
[INFO] Skip relation: 'null.schema_a.my_user.id <- null.schema_b.booking.created_by # 1' because it related to objects from other catalog/schema
[INFO] relation primary key: 'null.schema_a'
[INFO] primary key entity: 'null.schema_a'
[INFO] relation foreign key: 'null.schema_b'
[INFO] foreign key entity: 'null.schema_b'
The problem is that Booking#createdBy is not MyUser.
I've searched on SO and official documentation, but without success. Is there any way to achieve this? I know that another option is to move all tables into single schema, but that is almost unfeasible for our project.
You are correct, Cayenne just skips cross-schema relationships.
This looks like an obsolete limitation that should be dropped.
If you are doing this only once, you can just add these missing relationships in Cayenne Modeler. If you need to synchronize Cayenne model with your DB periodically, then it's can be harder to overcome.
One option is to skip relationship loading altogether (see docs), and create them manually (or with "Infer relationships" tool in Modeler). In this case all other content (tables and columns) should synchronize just fine. Another option is to wait for 4.1.M2 version of Cayenne that I believe will be ready soon (no exact dates though)
Related
I'm using Hibernate to create SQLite tables.
I have a table as such
#Entity
class Person(
#Column(unique = true, nullable = false)
val name: String,
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Int? = null,
)
I see that when the database is created, the unique constraint is added later on via an ALTER request
Hibernate: create table Person (id integer, name varchar(255) not null, primary key (id))
Hibernate: alter table Person add constraint UK_is4vd0f6kw9sw4dxyie5cy9fa unique (name)
Except that SQLite does not seem to support ALTER requests modifying constraints on tables.
So my question is : Is there a way to literally indicate Hibernate to set that uniqueness constraint on table creation? What would be the best way to do it?
I can ensure uniqueness easily later on via code, but I'd rather use the power of the database if I can.
I should add that this is for a personal small application so so far I'm using the update setting for hibernate.hbm2ddl.auto so Hibernate generates the SQL itself. I'm open to other methods but I'd rather avoid them if I can to reduce maintenance.
Gonna answer my own question given that it's not getting much traction :).
SQLite indeed does not support ALTER with constraints, and Hibernate does not (to my knowledge) offer a clean way to use custom SQL.
On top of this, it is not recommended to use Hibernate: hbm2ddl.auto=update in production.
For those reasons, I decided to turn myself to Flyway and write my own SQL. The good news is that adding Flyway provides my database migrations. The bad news is that it's one more dependency to maintain.
What I've done:
Added the flyway dependency in my build.gradle.kts
implementation("org.flywaydb:flyway-core:8.4.3")
Instantiated flyway before hibernate in my code, pointing to the same database:
val flyway = Flyway
.configure()
.dataSource("jdbc:sqlite:test.db", null, null).load()
flyway.migrate()
Added a handwritten SQL migration file in resources/V1__Create_person_and_entries_table.sql
create table Person
(
id integer primary key,
name varchar(255) not null UNIQUE
);
Et voilà!
I wrote a blog post over here with more details and some more reasons to use something like Flyway.
I am fetching products and categories from other source. I want to persist them in broadleaf database. I don't want auto generated ids. I want to persist id comes from source(from where I am fetching products etc.) to broadleaf database.
For that I have created my custom controller : http://www.broadleafcommerce.com/docs/core/current/broadleaf-concepts/admin/admin-custom-controllers
I am writing product and category persisting code in MyController.
I have tried following code :
Category category = catalogService.createCategory();
Long categoryId = new Long(453510);
category.setId(categoryId);
category.setName("test category4");
category.setUrl("/test-category4");
catalogService.saveCategory(category);
Product p = catalogService.createProduct(ProductType.PRODUCT);
Sku newSku = catalogService.createSku();
Long skuId = new Long(453520);
newSku.setId(skuId);
p.setDefaultSku(newSku);
p.getDefaultSku().setDefaultProduct(p);
p.setName("test product4");
p.setUrl("/test-product4");
p.setCategory(category);
Long productId = new Long(453530);
p.setId(productId);
catalogService.saveProduct(p);
I am getting following stacktrace :
Caused by: org.postgresql.util.PSQLException: ERROR: insert or update on table "blc_sku" violates foreign key constraint "fk28e82cf77e555d75"
Detail: Key (default_product_id)=(453530) is not present in table "blc_product".
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2182)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1911)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:173)
at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:645)
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:495)
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeUpdate(AbstractJdbc2Statement.java:441)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
If I remove below snippet, then it gives me null pointer because product object required to set default sku.
Sku newSku = catalogService.createSku();
Long skuId = new Long(453520);
newSku.setId(skuId);
p.setDefaultSku(newSku);
p.getDefaultSku().setDefaultProduct(p);
Please help me to persist product with given id( not autogenerated) in broadleaf database.
Because Hibernate is configured to generate the values for the id attributes on these entities, Hibernate is forcing the next generated value even though you have attempted to override the value. It appears that your override value is being used for the foreign key and Hibernate's generated value is being set for the primary key resulting in the FK constraint violation.
These entities (category, product, sku) have been designed with the assumption that users would be importing from external systems and because of that, they each have an externalId attribute defined. The recommended approach is to let Hibernate manage the pk/fk values via the generator and then the externalId attributes are used for mapping external systems into these entities.
If your requirement does not allow the use of the externalId, you could look at trying to override the generator annotation on those attributes using load time weaving. There is no Broadleaf mechanism to override an existing annotation on an attribute so this would be a customization you would have to explore.
In my spring project, the tables in database are created automatically by Hibernate using my entity classes as base, but I insert some default values in the table manually (using pgAdmin3).
Because that, I am facing now this problem: when I try insert a value via Java code in one of the tables which already have values, I receive a error message, saying the primary key already exists in the database.
Anyone knows how to solve this problem?
UPDATE
That's how I declare my primary key in my class:
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
Call this SQL query once per table to set the sequence to the next free number:
SELECT setval('tblname_id_seq', max(id)) FROM tblname;
tblname being the actual name of the table.
Hibernate may use a different naming convention, or the sequence may have been renamed. If you can't find the sequence behind the serial column, check with (per documentation):
SELECT pg_get_serial_sequence(tblname, column_name)
More details:
Modify Django AutoField start value
How to import a CSV to postgresql that already has ID's assigned?
The problem here might be that you declare the id as a primitive instead of a wrapper.
So instead of:
private int id;
You should have:
private Integer id;
When you create the entity with the id is initialized as 0, instead of NULL.
That's why you get duplicate id constraint violation exceptions.
Only when the id is NULL the AUTO generation strategy will delegate the id assignment to the database.
I am using Hibernate 4.1.3 (JPA) on the Play! framework. The database is PostgreSQL 8.4.2. The schema was generated using hibernate.hbm2ddl.auto="update".
Short version: I have a class that has an #Id field that is a #GeneratedValue. Sometimes, when persisting it, I get a null-column violation, why?
More details:
I have a really simple class that I want to save to the database, that looks like this:
#Entity
class MyObject {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long id;
#NotNull
public String email;
public Integer total;
}
I usually create an instance of MyObject, I assign a value to email and total fields while id is null and I save it via EntityManager.persist(). Hibernate gets an id for the new object and saves it to the DB.
However sometimes, I get the following stacktrace:
2012-05-19 00:45:16,335 - [ERROR] - from org.hibernate.engine.jdbc.spi.SqlExceptionHelper [SqlExceptionHelper.java:144] in play-akka.actor.actions-dispatcher-6
ERROR: null value in column "id" violates not-null constraint
2012-05-19 00:45:16,350 - [ERROR] - from application in play-akka.actor.actions-dispatcher-6
! #6ad7j3p8p - Internal server error, for request [POST /method] ->
play.core.ActionInvoker$$anonfun$receive$1$$anon$1: Execution exception [[PersistenceException: org.hibernate.exception.ConstraintViolationException: ERROR: null value in column "id" violates not-null constraint]]
How is this possible? How can I track down the problem?
Here's the relevant DDL generated by Hibernate:
CREATE TABLE myobject (
id bigint NOT NULL,
email character varying(255) NOT NULL,
physical integer
);
CREATE SEQUENCE hibernate_sequence
START WITH 1
INCREMENT BY 1
NO MAXVALUE
NO MINVALUE
CACHE 1;
ALTER TABLE ONLY dailydetailedscore
ADD CONSTRAINT dailydetailedscore_pkey PRIMARY KEY (id);
Try the annotation #org.hibernate.annotations.GenericGenerator(name = “test-hilo-strategy”, strategy = “hilo”):
#Id
#org.hibernate.annotations.GenericGenerator(name=“hilo-strategy”, strategy = “hilo”)
#GeneratedValue(generator = ”hilo-strategy”)
As someone noted above, AUTO does not do what you think. It uses the underlying DB to determine how to generate values. It may pick sequences (for oracle), identity column (for mssql), or something else that is db specific.
The approach here uses an internal strategy that Hibernate supplies called "hilo".
See chapter 5 of the Hibernate reference manual dealing with "Generator" for a full description of what each of the supplied ones does.
Neither the OP solution nor Matt's solution worked with my PostgreSQL 9.3.
But this one works:
#SequenceGenerator(name="identifier", sequenceName="mytable_id_seq", allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="identifier")
Replace mytable_id_seq with the name of the sequence that generates your id.
Use Hibernate method:- save(String entityName, Object object)
Persist the given transient instance, first assigning a generated identifier.
Do not use :- #GeneratedValue(strategy=GenerationType.IDENTITY) for primary key if you want to persist user define Id.
For detail:-
http://docs.jboss.org/hibernate/core/3.6/javadocs/org/hibernate/Session.html#save(java.lang.String
In mycase i was using Identity generation strategy and i have set the wrong data type in Postgres. Following steps i performed to debug the problem.
set
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true in application.properties and Drop the tables.
By this hibernate will automatically create the schema for you on the basis of your data-type.I noticed change in the datatype of id field.
Now when i tried any post requests, everything worked fine.
Abridged version of my schema:
utility_company
id int not null -- PK
name varchar(255) not null
utility_settings
utility_id -- FK to utility
use_magic tinyint(1) not null default 0
There is a one-to-one mapping between these two tables. Setting aside the fitness of this design, I want to Map the data in both of these tables to one object. In Hibernate/JPA, this is allegedly done as follows:
#Entity
#Table(name = "utility_company")
#SecondaryTables({
#SecondaryTable(
name = "utility_settings",
pkJoinColumns = {
#PrimaryKeyJoinColumn(
name="utility_id", referencedColumnName="id")
})
})
public class UtilityCompany extends AbstractEntity {
And so forth.
Every #Column includes the appropriate table name.
When I deploy, I get this error:
Cannot find the expected secondary table:
no utility_company available for poscore.model.UtilityCompany
The utility_company table is definitely there (a previous version only maps UtilityCompany to the utility_company table; I'm adding the utility_settings).
Found numerous forum posts with this exact problems and no answers. I've also tried various allegedly legal forms of specifying the #SecondaryTable all of which have the same effect.
Anyone successfully use #SecondaryTable, and, if so, seen this?
"Every #Column includes the appropriate table name."
Try removing the explicit table name for the first table name columns, only specifying it for the secondary table columns. Did the trick for me.
Your mappings are correct IMHO, and runs fine with DataNucleus AccessPlatform as the JPA implementation. Maybe Hibernates log tells you more ?
--Andy DataNucleus