I have recently started using Cassandra for my Spring Boot applications. I have always just used #PrimaryKeyColumn and #Id annotation to mark the Primary Key in the Java Class but yesterday I saw a github repos where people were using #PrimaryKeyColumn annotations with #Id annotation and some other repos where people are just using #PrimaryKey annotations.
What am I missing here, I feel so lost.
A good example for this can be found in the Git repo for DataStax's E-commerce workshop.
Consider this table/PK definition:
CREATE TABLE user (
user_id UUID,
...
PRIMARY KEY (user_id));
As there is a single column defined as the primary key, the UserEntity class only needs to use the #PrimaryKey annotation:
#PrimaryKey("user_id")
private UUID userId;
Now consider this table/PK definition:
CREATE TABLE cart_products (
cart_id uuid,
product_timestamp timestamp,
product_id text,
...
PRIMARY KEY (cart_id, product_timestamp, product_id)
) WITH CLUSTERING ORDER BY (product_timestamp DESC, product_id ASC)
AND default_time_to_live = 5184000;
cart_products uses three columns for its PRIMARY KEY: cart_id as the partition key; product_timestamp and product_id for the clustering keys. So the CartProductEntity class still uses the #PrimaryKey annotation, but it references the CartProductsPrimaryKey class:
#PrimaryKey
private CartProductsPrimaryKey key;
Inside that class, the individual primary key columns are annotated with #PrimaryKeyColumn:
#PrimaryKeyClass
public class CartProductsPrimaryKey {
#PrimaryKeyColumn(
name = "cart_id",
ordinal = 0,
type = PrimaryKeyType.PARTITIONED)
private UUID cartId;
#PrimaryKeyColumn(
name = "product_timestamp",
ordinal = 1,
type = PrimaryKeyType.CLUSTERED,
ordering = Ordering.DESCENDING)
private Date productTimestamp;
#PrimaryKeyColumn(
name = "product_id",
ordinal = 2,
type = PrimaryKeyType.CLUSTERED,
ordering = Ordering.ASCENDING)
private String productId;
Basically, the annotations used depend largely on the complexity of the primary key definition.
As for the #Id annotation, it serves the same function as #PrimaryKey. This is mentioned in the Spring Data Cassandra repository.
The #Id annotation tells the mapper which property you want to use for the Cassandra primary key. Composite primary keys can require a slightly different data model.
https://github.com/spring-projects/spring-data-cassandra/blob/main/src/main/asciidoc/reference/mapping.adoc#metadata-based-mapping
#Id: Applied at the field or property level to mark the property used for identity purposes.
#PrimaryKey: Similar to #Id but lets you specify the column name.
#PrimaryKeyColumn: Cassandra-specific annotation for primary key columns that lets you specify primary key column attributes, such as for clustered or partitioned. Can be used on single and multiple attributes to indicate either a single or a composite (compound) primary key. If used on a property within the entity, make sure to apply the #Id annotation as well.
#PrimaryKeyClass: Applied at the class level to indicate that this class is a compound primary key class. Must be referenced with
Related
consider an entity with just an id and text field:
#lombok.Data
class Entity {
#javax.persistence.Id
UUID id;
String name;
}
consider that the table definition is as follows:
create table entity (
id uniqueidentifier not null primary key default newid(),
name varchar(max)
);
I am then curious why this doesn't work and how i could make it work:
UUID savedId = entityRepository.save(new Entity().setName("entity name")).getId();
In JPA, entity IDs can be either assigned by the application code, or generated (by the JPA provider, such as Hibernate, or by the database). In many situations, it's desirable to have the entity IDs be generated instead of applicaiton-assigned; it seems like that's what you are expecting.
If so, you need to annotate the id field with #GeneratedValue. For example:
class Entity {
#Id
#GeneratedValue
UUID id;
String name;
}
Note that there are considerations to be made regarding the generation strategy, so you'll want to educate yourself about them and make the right choice based on your situation. This is a good page that discusses the options. This SO Answer also is worth reading (the author is a well-known expert on JPA and Hibernate).
In Spring JPA I havean entity and I init the schema using FlywayDb.
My entity is:
#Entity
#Table(schema = "scheduler",
uniqueConstraints={#UniqueConstraint(name = "uq_task", columnNames = {"task", "date_at"})}
)
public class Task {
#Id
private Long id;
#Embedded
#Column(nullable = false)
private ITask task;
#Column(nullable = false)
private Date dateAt;
}
The schema is initialized as follows:
CREATE SCHEMA scheduler;
CREATE TABLE scheduler.task (
id bigserial primary key,
task bytea NOT NULL,
date_at timestamp NOT NULL
);
CREATE UNIQUE INDEX uq_task
ON scheduler.task(task, date_at);
Without the constraints on the entity, it works, with it doesn't. In particular I have the exception:
Caused by: org.hibernate.AnnotationException: Unable to create unique key constraint (task, date_at) on table task: database column 'task' not found. Make sure that you use the correct column name which depends on the naming strategy in use (it may not be the same as the property name in the entity, especially for relational types)
at org.hibernate.cfg.Configuration.buildUniqueKeyFromColumnNames(Configuration.java:1684)
at org.hibernate.cfg.Configuration.buildUniqueKeyFromColumnNames(Configuration.java:1616)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1452)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1846)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:857)
I use an H2 database.
ITask is an interface with several POJO implementations. ITask interface is annotated with #Embeddable.
My guess is that JPA tries to apply the unique constraints on columns that are not yet created by FlywayDb library. But this makes no sense to me.
Any idea?
After update of you question now I can guess that there is a problem with attribute in your ITask insterface please read that doc. In my opinion you have to override embbedable entity attribute to fix your problems.
I am using hibernate only with Annotations. My table looks something like this:
#Entity
#Table(name = "NetworkType",
uniqueConstraints = {#UniqueConstraint(columnNames = {"network_id", "type"})})
public class NetworkType implements Serializable {
#Id
private long id;
#Column(name = "network_id", nullable = false)
private long networkId;
#Column(name = "type", nullable = false)
private String type;
...
Currently when I write the same NetworkType twice, it throws an exception due to the UniqueConstraint (which is expected).
My thoughts are to just read the item first before checking. The problem is, my primary key is the Id, which I need because other tables references this table.
What's the best way to query for item for the "network_id" and "type" to verify the combination doesn't already exist?
I know I can do this with a Query manually, but is there a more Hibernate-y way of doing it?
In general, what's the proper way to "get" an object without using the PK? Are Criteria or Query the best way?
#UniqueConstraint is mainly used by database schema generation tools to create the data base schema. If used, they will generate the table with the columns mentioned in the #UniqueConstraint having unique constraint defined.
#UniqueConstraint doesn't have any impact/usage during data manipulation.
If you wish to achieve unique constraint behavior on network_id and type columns and your schema is already created, update your database schema to add the unique constraint on network_id and type columns. as below:
ALTER TABLE NetworkType
ADD CONSTRAINT uc_network_id_type UNIQUE (network_id, type)
Hope this helps!
I have an entity product:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "product")
#MapKeyColumn(name="locale")
public Map<String,ProductNames> getProductNames() {
return this.productNames;
}
and an entity named productNames which contains the localized versions of the product
private int id;
private SkiProduct skiProduct;
private String locale;
private String text;
I tried the #MapKeyColumn and the #MapKey annotation.
The generated SQL looks like this:
select
productnam0_.product_id as product4_1_,
productnam0_.id as id1_,
productnam0_.mapkey as mapkey1_,
productnam0_.id as id231_0_,
productnam0_.locale as locale231_0_,
productnam0_.product_id as product4_231_0_,
productnam0_.text as text231_0_
from
product_names productnam0_
My question is how to configure this mapping correctly. The mapkey column in the SQL-statement leads to a SQL-Errormsg since it is not in the db-table.
Type (ROW(id integer, locale char(2), text varchar(255), product_id integer)) not found.
EDIT: The Javadoc says: "If a persistent field or property other than the primary key is used as a map key then it is expected to have a uniqueness constraint associated with it. "
So I wonder whether I could use Maps for this ?
A Map key is supposed to be unique in Java, and that's reflected in JPA expecting a unique key in the database to map to the key for the Map.
If you can't guarantee that uniqueness, you need to use another datastructure or use another key.
If possible, set up a unique key on the fields you want to be the key, that should fix your problem (but you would possibly end up having to use a compound key, and thus a different Map structure in your Java code, for example you might have a mapkey+language_id compound key that's unique).
What's the exact difference between #JoinColumn and #PrimaryKeyJoinColumn?
You use #JoinColumn for columns that are part of a foreign key. A typical column could look like (e.g. in a join table with additional attributes):
#ManyToOne
#JoinColumn(name = "...")
private OtherClass oc;
What happens if I promote the column to be a/the PK, too (a.k.a. identifying relationship)? As the column is now the PK, I must tag it with #Id:
#Id
#ManyToOne
#JoinColumn(name = "...")
private OtherClass oc;
Now the question is:
Are #Id + #JoinColumn the same as just #PrimaryKeyJoinColumn?:
#ManyToOne
#PrimaryKeyJoinColumn(name = "...")
private OtherClass oc;
If not, what's #PrimaryKeyJoinColumn there for?
What happens if I promote the column to be a/the PK, too (a.k.a. identifying relationship)? As the column is now the PK, I must tag it with #Id (...).
This enhanced support of derived identifiers is actually part of the new stuff in JPA 2.0 (see the section 2.4.1 Primary Keys Corresponding to Derived Identities in the JPA 2.0 specification), JPA 1.0 doesn't allow Id on a OneToOne or ManyToOne. With JPA 1.0, you'd have to use PrimaryKeyJoinColumn and also define a Basic Id mapping for the foreign key column.
Now the question is: are #Id + #JoinColumn the same as just #PrimaryKeyJoinColumn?
You can obtain a similar result but using an Id on OneToOne or ManyToOne is much simpler and is the preferred way to map derived identifiers with JPA 2.0. PrimaryKeyJoinColumn might still be used in a JOINED inheritance strategy. Below the relevant section from the JPA 2.0 specification:
11.1.40 PrimaryKeyJoinColumn Annotation
The PrimaryKeyJoinColumn annotation
specifies a primary key column that is
used as a foreign key to join to
another table.
The PrimaryKeyJoinColumn annotation
is used to join the primary table of
an entity subclass in the JOINED
mapping strategy to the primary table
of its superclass; it is used within a
SecondaryTable annotation to join a
secondary table to a primary table;
and it may be used in a OneToOne
mapping in which the primary key of
the referencing entity is used as a
foreign key to the referenced
entity[108].
...
If no PrimaryKeyJoinColumn
annotation is specified for a subclass
in the JOINED mapping strategy, the
foreign key columns are assumed to
have the same names as the primary key
columns of the primary table of the
superclass.
...
Example: Customer and ValuedCustomer subclass
#Entity
#Table(name="CUST")
#Inheritance(strategy=JOINED)
#DiscriminatorValue("CUST")
public class Customer { ... }
#Entity
#Table(name="VCUST")
#DiscriminatorValue("VCUST")
#PrimaryKeyJoinColumn(name="CUST_ID")
public class ValuedCustomer extends Customer { ... }
[108] The derived id mechanisms
described in section 2.4.1.1 are now
to be preferred over
PrimaryKeyJoinColumn for the
OneToOne mapping case.
See also
Primary Keys through OneToOne Relationships
This source http://weblogs.java.net/blog/felipegaucho/archive/2009/10/24/jpa-join-table-additional-state states that using #ManyToOne and #Id works with JPA 1.x. Who's correct now?
The author is using a pre release JPA 2.0 compliant version of EclipseLink (version 2.0.0-M7 at the time of the article) to write an article about JPA 1.0(!). This article is misleading, the author is using something that is NOT part of JPA 1.0.
For the record, support of Id on OneToOne and ManyToOne has been added in EclipseLink 1.1 (see this message from James Sutherland, EclipseLink comitter and main contributor of the Java Persistence wiki book). But let me insist, this is NOT part of JPA 1.0.
I normally differentiate these two via this diagram:
Use PrimaryKeyJoinColumn
Use JoinColumn
I know this is an old post, but a good time to use PrimaryKeyColumn would be if you wanted a unidirectional relationship or had multiple tables all sharing the same id.
In general this is a bad idea and it would be better to use foreign key relationships with JoinColumn.
Having said that, if you are working on an older database that used a system like this then that would be a good time to use it.
You use #JoinColumn when you want to manage (change the column name, set nullable and so on) the foreign key column in the target entity table. Here, the Address table will contains User table id like foreign key but the column it's will be name user_id (the second scheme of #Sam YC)
#Entity
public class Address implements Serializable {
#Id
#GeneratedValue
private String id;
private String city;
#OneToOne(optional = false)
#JoinColumn(name = "user_id", updatable = false)
private User user;
}
You use #PrimaryKeyJoinColumn, when you want to use the primary key of the referencing entity like the target entity primary key. Here the Address know the referencing User but Address table hasn't foreign key column, because it's has the same id than User Id (the first scheme of #Sam YC)
#Entity
public class Address implements Serializable {
#Id
#GeneratedValue(generator = "foreignKeyGenerator")
#GenericGenerator(
name = "foreignKeyGenerator",
strategy = "foreign",
parameters = #org.hibernate.annotations.Parameter(
name = "property", value = "userT"
)
)
private String id;
private String city;
#OneToOne(optional = false)
#PrimaryKeyJoinColumn
private User userT;
}