I cannot make MySQL column values unique, Spring Data JPA - mysql

When I try to make a column value, unique = true I get the following error.
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "
alter table student
add constraint UK_fe0i52si7ybu0wjedj6motiim unique (email)" via JDBC Statement
....
....
Caused by: java.sql.SQLSyntaxErrorException: BLOB/TEXT column 'email' used in key specification without a key length
This is my application properties file.
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/dataJpaDB
spring.datasource.username=root
spring.datasource.password=1234
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL8Dialect
spring.jpa.properties.hibernate.format_sql=true
server.error.include-message=always
I'm using MySQL version 8.
This is my entity class
#Data
#Entity(name = "Student")
public class Student {
#Id
#SequenceGenerator(
name = "student_sequence",
sequenceName = "student_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "student_sequence"
)
#Column(
name = "id",
updatable = false
)
private Long id;
#Column(
name = "first_name",
nullable = false,
columnDefinition = "TEXT"
)
private String firstName;
#Column(
name = "last_name",
nullable = false,
columnDefinition = "TEXT"
)
private String lastName;
#Column(
name = "email",
nullable = false,
columnDefinition="TEXT",
unique = true /// this is causing the problem
)
private String email;
#Column(
name = "age",
nullable = false,
columnDefinition = "INTEGER"
)
private Integer age;
public Student(Long id, String firstName, String lastName, String email, Integer age) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.age = age;
}
}
When I remove the "unique = true" everything seems to work fine.
What is causing this problem and how to resolve it.
P.S. sorry for the long post.

When you specify that a field must be unique, Hibernate ORM creates a constraint on the database.
But different databases might have different requirements about the creation of the constraint based on the column definition.
Checking the MySQL website about BLOB and Text types:
For indexes on BLOB and TEXT columns, you must specify an index prefix length.
The easiest solution is not to touch the columnDefinition attribute:
#Column( name = "email",
nullable = false,
unique = true)
private String email
Not sure why you specify it, but you should only use that attribute if you want to define a column using a type that's different from the default used by Hibernate ORM. In this case the column will be a varchar(255) and everything will work fine.
If you want the column to be a TEXT and you still want to create the schema using Hibernate ORM, you could add the constraint creation in the import.sql (and remove the attribute unique=true).
Hibernate ORM will execute the content of that file after the schema has been created and if you include the following query in it, it will create the constraint:
alter table Student add constraint my_unique_constraint unique (email(10))
I will let you to the Hibernate ORM guide for more details about schema generation
Keep in mind that this last solution is not portable and you will have to update the constraint creation query every time you change database (or the attribute name).
PS: I'm talking about Hibernate ORM in this answer but I should have used JPA or Spring Data. Anyway, everything still applies.

Related

Is a Spring-data-jpa entity already indexed by the #EmbeddedId field?

I have the following spring-data-jpa entity:
#Entity(name = "absenceDays")
#Table
public final class MyTable {
#EmbeddedId
private MyId myId;
#Column(nullable = false)
private Long anotherId;
}
further, this is the #Embeddable entity used above:
public final class MyId implements Serializable {
#Column(updatable = false, nullable = false)
private Long id;
#Column(updatable = false, nullable = false)
private LocalDate date;
}
I have couple of questions?
Are tables already indexed with their primary keys? It seems to be implementation specific, as discussed here When should I use primary key or index?
How should I index my table with the composite id using the JPA 2.1 #Index annotation, if I need to index my table?
My DB of choice will be AWS RDS with MySQL InnoDB dialect.
Since you tagged the Question [mysql], I will address it from that point of view.
In MySQL the PRIMARY KEY is always UNIQUE and a KEY (aka INDEX). In the case of ENGINE=InnoDB, it is also "clustered" with the data. This makes fetching a row, given the PK, very fast.
To ask questions related to MySQL, it is best to dig below the 3rd party interface (Spring, in your case) to get to the MySQL (or MariaDB or Aurora) info, such as CREATE TABLE and SELECT....
I could probably answer your Q2 with the above info.

Unknown table 'hibernate_sequence' in field list

I have strange error here...
I have two databases configurated on this project, and when i try to save into local mysql repository, i get the title error.
In addition i have remote oracle db in use.
Hibernate:
select
hibernate_sequence.nextval
from
dual
and then
[nio-8080-exec-9] o.h.engine.jdbc.spi.SqlExceptionHelper : Unknown table 'hibernate_sequence' in field list
and there is no table named hibernate.sequence in database, or attribute in class.
#Id
#GeneratedValue
long id;
#Column(name = "customerid")
private String customerid;
#OneToMany(targetEntity = C_Portfolio.class, fetch = FetchType.LAZY, cascade = {CascadeType.ALL}, orphanRemoval=true)
#Fetch(FetchMode.SELECT)
private List<C_Portfolio> portfolios;
#Column(name = "date")
private LocalDate date;
#Column(name = "date_time")
private LocalDateTime datetime;
Furthermore everything seems to be okey just before the save. When i check the class to be saved in debugger mode. it has all the needed values and everything seems to be okey.
You have simply used #GeneratedValue in the POJO, so it tries to find the sequence table. Since you haven't specified the sequence table name, it will look for default sequence table "hibernate_sequence".
For mysql it increments the value by specifying as like below in your POJO:
#GeneratedValue(strategy = GenerationType.AUTO)
If you use it, it will generate a table called hibernate_sequence to provide the next number for the ID sequence.

Hibernate: do not delete child collection on parent removal

I have one of the weird issues that make you growl. I'm having (also weird) Spring/Hibernate application, that is intended to manage database in following way (i've simplified some things, so don't be confused that source code mentions slightly different tables/columns):
active_proxy_view table:
id | entity
<uuid> | <string containing json>
archive_proxy_view table:
id | entity
<uuid> | <string containing json>
track_reference table:
ref_type | ref_id | track_domain | track_type | track_id |
'proxy' | <uuid> | 'example.com' | 'customer' | '123' |
Keeping two tables is mandatory - i need to have both all-time-history/statistical queries and business-value queries only for things that being active right now, so i need to keep set for active proxies tight. track_reference table is used for searches so i could do queries like that:
SELECT p.id, p.entity FROM archive_proxy_view AS p
INNER JOIN track_reference AS t1 ON
t1.ref_id = p.id AND
t1.ref_type = 'proxy' AND
t1.track_domain = 'example.com' AND
t1.track_type = 'customer' AND
t1.track_id = '123'
INNER JOIN track_reference AS t2 ON
t2.ref_id = p.id AND
t2.ref_type = 'proxy' AND
t2.track_domain = 'example.com' AND
t2.track_type = 'campaign' AND
t2.track_id = 'halloween-2017'
(it may be not 100% correct, i haven't raw sql experience in a while)
And here's the problem:
Both active_proxy_view and archive_proxy_view entities are inherited from one class that specifies #OneToMany relationship on track_reference entity; #ManyToOne usage is not really possible, because there are many entities tied to tracking reference
track_reference is managed separately (and this is mandatory too)
I need to manage views separately from track_reference table, but whenever i tell Hibernate to remove entity from active_proxy_view table, it takes away track_reference entities as well. Even if i play with cascade annotation value, which is blank by default (and as i understand, it means that child records should not be deleted with parent). There is possibility that i've missed something, though.
I also failed to hack the whole thing using custom #SQLDeleteAll, i still can see regular deletes in general log:
55 Query delete from tracking_reference where referenced_entity_id='13c6b55c-f9b7-4de7-8bd4-958d487e461c' and referenced_entity_type='proxy' and tracked_entity_type='agent'
55 Query delete from tracking_reference where referenced_entity_id='13c6b55c-f9b7-4de7-8bd4-958d487e461c' and referenced_entity_type='proxy' and tracked_entity_type='lead'
55 Query delete from tracking_reference where referenced_entity_id='13c6b55c-f9b7-4de7-8bd4-958d487e461c' and referenced_entity_type='proxy' and tracked_entity_type='source'
53 Query DELETE FROM `tracking_reference` WHERE `referenced_entity_type` = 'proxy' AND referenced_entity_id = '13c6b55c-f9b7-4de7-8bd4-958d487e461c' AND 1 = 0
I'm using Hibernate 5.2.3.Final through Spring 4.3.2.RELEASE / Spring Data JPA 1.10.2.RELEASE
TL; DR
So, the question is: how do i prevent Hibernate from deleting associated entities when parent is deleted?
The source code for entities looks like this:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class ProxyViewEntryTemplate {
#Id
#NotNull
#Column(nullable = false)
private String id;
#NotNull
#Column
private String entity;
// some other columns
#OneToMany
#JoinColumn(name = TrackRef.REFERENCE_ID_COLUMN_NAME) // 'reference_entity_id`
#Where(clause = ProxyView.TRACK_WHERE_JOIN_CLAUSE) // `referenced_entity_type` = 'proxy'
#SQLDeleteAll(sql = ProxyView.TRACK_DELETE_ALL_QUERY) // DELETE FROM `tracking_reference` WHERE `referenced_entity_type` = 'proxy' AND referenced_entity_id = ? AND 1 = 0
private Collection<TrackingReference> track = new ArrayList<>();
// setters, getters, hashCode, equals
}
#Entity
#Table(name = "active_proxy")
public class ActiveProxyViewEntry extends ProxyViewEntryTemplate {}
#Entity
#Table(name = "tracking_reference")
#IdClass(TrackingReferenceId.class)
public class TrackingReference {
#Id
#Column(name = "tracked_entity_type", nullable = false)
#NotNull
private String trackedType;
#Id
#Column(name = "tracked_entity_domain", nullable = false)
private String trackedDomain;
#Id
#Column(name = "tracked_entity_id", nullable = false)
private String trackedId;
#Id
#Column(name = "referenced_entity_type", nullable = false)
#NotNull
private String referencedType;
#Id
#Column(name = "referenced_entity_id", nullable = false)
#NotNull
private String referencedId;
// setters, getters, hashCode, equals
}
The whole thing is managed through Spring JPA Repositories:
#NoRepositoryBean
public interface SuperRepository<E, ID extends Serializable> extends PagingAndSortingRepository<E, ID>,
JpaSpecificationExecutor<E> {
}
public interface ActiveProxyViewRepository extends SuperRepository<ActiveProxyViewEntry, String> {}
// the call for deletion
public CompletableFuture<Void> delete(ID id) {
...
descriptor.getRepository().delete(descriptor.getIdentifierConverter().convert(id));
...
}
// which is equal to
...
ActiveProxyViewRepository repository = descriptor.getRepository();
String uuidAsString = descriptor.getIdentifierConverter().convert(id);
repository.delete(uuidAsString);
...
If you remove the #JoinColumn, you shouldn't have this problem.
If you need to keep the #JoinColumn, you need to remove the foreign-key constraint requirement that gets automatically applied by the persistence provider by changing the annotation to:
#JoinColumn(
name = "yourName"
foreignKey = #Foreignkey(value = ConstraintMode.NO_CONSTRAINT)
)
You should then be able to delete the view entity without forcing the tracking references to be removed.
It turned out to be a typical shoot yourself in the foot scenario.
Tracking references were updated in a rather sophisticated way:
Build collection of references to be stored in database (C1)
Load all present references (C2)
Store C1
Delete all references that are present in C2 but not referenced in C1 (using collection.removeAll)
And it turned out that my .equals method has been written terribly wrong, returning false in nearly in each case. Because of that, usually every reference was deleted from database (the queries you can see in the log in question), so it was my fault.
After i've fixed that, only #SQLDeleteAll query was run - for reasons not known to me, it still acted like if cascade option was set. I managed to get rid of it using #OneToMany(updatable = false, insertable = false); it seems like a dirty hack, but i don't have enough time to dig it through.
I haven't tested it thoroughly yet, but, i hope, that solves the problem.

JPA doesn't t allow entity made of columns from multiple tables?

I know this makes none sense as many tutorials state that you can use SecondaryTable annotation, however it doesn't work in hibernate. I have schema like this:
#Entity
#Table(name="server")
#SecondaryTable(name="cluster", pkJoinColumns = { #PrimaryKeyJoinColumn(name = "uuid", referencedColumnName = "cluster_uuid") })
public class Server {
#Id
#Column(name = "uuid")
private String uuid;
#Column(name = "cluster_uuid")
private String clusterUuid;
#Column(name = "ip", table="cluster")
private String ip;
..... }
#Entity
#Table(name = "cluster")
public class Cluster {
#Id
#Column(name = "uuid")
private String uuid;
#Column(name = "ip")
private String ip;
.....
}
Server.clusterUuid is a foreign key to Cluster.uuid. I am hoping to get Server entity that fetches ip column from Cluster by joining Server.clusterUuid to Cluster.uuid.
Then I was greeted by a hibernate exception:
Caused by: org.hibernate.AnnotationException: SecondaryTable
JoinColumn cannot reference a non primary key
at org.hibernate.cfg.annotations.TableBinder.bindFk(TableBinder.java:402)
at org.hibernate.cfg.annotations.EntityBinder.bindJoinToPersistentClass(EntityBinder.java:620)
at org.hibernate.cfg.annotations.EntityBinder.createPrimaryColumnsToSecondaryTable(EntityBinder.java:612)
I see lots of people encountered this problem. But the first bug for this in Hibernate's bugzilla was 2010, I am surprised it's been there for over two years as this is supposed to be a basic feature. There is some post saying JPA spec only allows primary key to do the mapping, however, I get below from JPA wikibook
JPA allows multiple tables to be assigned to a single class. The
#SecondaryTable and SecondaryTables annotations or
elements can be used. By default the #Id column(s) are assumed to be
in both tables, such that the secondary table's #Id column(s) are the
primary key of the table and a foreign key to the first table. If
the first table's #Id column(s) are not named the same the
#PrimaryKeyJoinColumn or can be used to
define the foreign key join condition.
it's obviously OK for non-primary key. Then I am confused why Hibernate didn't fix this problem as it seems to be easy to implement by a join clause.
anybody knows how to overcome this problem? thank you.
I don't quite understand your setup.
#SecondaryTable is for storing a single entity in multiple tables, but in your case you have a many-to-one relationship between different entities (each one stored in its own table), and it should be mapped as such:
#Entity
#Table(name="server")
public class Server {
#ManyToOne
#JoinColumn(name = "cluster_uuid")
private Cluster cluster;
...
}

hibernate not generate auto increment constraint on mysql table

I have been searching through different forums for information and I have tried different solutions, but I'm still unable to correct my issue.
I am using hibernate4 annotations for mapping my entities. Auto increment key is not detected when tables are created using hibernate in mysql.
I have the following code:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(unique = true, nullable = false)
private int responseId;
I have also tried:
#Id
#GenericGenerator(name="generator", strategy="increment")
#GeneratedValue(generator="generator")
private int responseId;
With hibernate the id is automatically assigned to a row, but in the mysql table it has no AutoIncrement Constraint. I have to mark field as AI manually. This becomes problematic when I manually insert a record for testing or use jdbc statements for the table. Please let me know what I am missing in configuration that is preventing the hibernate id from assigning an AutoIncrement Contraint.
Use the IDENTITY generator, and use the columnDefinition attribute of #Column to specify the type of the column:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(columnDefinition = "MEDIUMINT NOT NULL AUTO_INCREMENT")
private int responseId;