Spring boot JPA: remove column on entity change - java

I am try to create table in MySQL database from java class using spring-boot-starter-data-jpa. It work pretty well except when I change/remove column name in java class. Here an example:
I have a class call "Staff" with 2 fields: id, name
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#Column(name = "name", length = 15)
private String name;
public Staff() {
}
// some setter and getter here
When I run my project, a "Staff" table generated exactly as I want with 2 columns: id, name. The problem is if I split "name" into "firstname" and "lastname" like this:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#Column(name = "firstname", length = 15)
private String firstname;
#Column(name = "lastname", length = 15)
private String lastname;
public Staff() {
}
//some getter and setter here
The "Staff" table now contain 4 columns (id, name, firstname, lastname) instead of 3. Then I need to remove the "name" column myself. Is there anyway to get rid of it automatically?

Looks like you use :
spring.jpa.properties.hibernate.ddl-auto=update
78.1 Initialize a database using JPA
spring.jpa.generate-ddl (boolean) switches the feature on and off and
is vendor independent. spring.jpa.hibernate.ddl-auto (enum) is a
Hibernate feature that controls the behavior in a more fine-grained
way. See below for more detail.
You can set spring.jpa.hibernate.ddl-auto explicitly and the standard
Hibernate property values are none, validate, update, create,
create-drop.
as you can see there is nothing new (different from hibernate)
ddl-auto=update - generate changes but it doen't drop not mapped columns .
For example you can have a table with 10 columns and mapping only for 3 of them , in this case auto mode might drop them but for hibernate / jpa full mapping table-entity in not mandatory. Here is jira ticket Alter and drop columns with hbm2ddl.auto=update created Nov 2011 and now status is 'not fixed'.
If you update db often (your domain model is changed) , you can use ddl/dml tools like liquibase , flywaydb. You describe db changes in xml file , and execute tool , all changes will be apply automatically (with auto control what already modifyed before and what should be modifed now).
Just recommendation :
Better use ddl tools if you don't want to guess why something is droped in production. hibernate.ddl-auto maily used for development , not for production. In production you can use none or validate - as they are safe.

Related

Update column name for existing Entity class using Hibernate Annotation

I have one column in my Entity Class like :
#Column(name = "createdBy", nullable = false)
private String createdBy;
Later i need to update column name.
Is there any way to update the column name using hibernate annotation which will not generate new column.
Nope. You should use annotation and SQL script to modify table.

Hibernate renames some columns at start with Spring Boot 1.4

I'm having some problems with my PostgreSQL database columns on some tables.
If I declare a column of String with a name like a_column_name Hibernate accepts the column of the table with the name a_column_name.
But my problem comes when I declare the column type to Integer or BigDecimal. At start, Hibernate creates a new column of type int4 or numeric(19, 2) with a name like acolumnname with NULL's.
Hibernate: alter table t_myTable add column acolumnname int4
or
Hibernate: alter table t_myTable add column acolumnname numeric(19, 2)
I've tried to set the name-strategy on the config file of Spring boot:
jpa:
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate.cache.use_second_level_cache: false
hibernate.cache.use_query_cache: false
hibernate.generate_statistics: true
naming.physical-strategy: PhysicalNamingStrategyStandardImpl
But with no result...
So my question is why Hibernate accepts the column name when it's of String type but doesn't like it when it's Integer or BigDecimal.
Regards.
---- Update 1 ----
After search the Entity to post here the code as Veselin Davidov asked, I've noticed that some columns are accepted while others not.
The entities are created from a company framework that parses a JSON with the DB structure (tables, fields, type of fields...) and generates the JAVA entity class. So after see the answer from SAM I've changed the code of the Entity template to add a #Column(name = "your_column_name"). Now, when Hibernate starts, it doesn't add the columns with the wrong names and uses the DB columns.
if your Hibernate entity class is like the following:
#Entity
public class Registration{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column
#Basic
private String name;
// getters and setters
}
then your registration table will have fields named with "id" and "name" as you have used auto-ddl=update. To avoid that you can specify the column names with #Column(name = "your_column_name") like below:
#Table(name = "registration")
#Entity
public class Registration{
#Id
#Column(name = "r_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Basic
#Column(name = "r_name")
private String name;
// getters and setters
}
If you are using Intellij IDEA then the IDE can generate the required Entity class.
You can go to your persistence tab -> right-click on your project name -> Generate Persistence Mapping -> By Database Schema. Then select the tables whose Entity you want to generate. Voila everything comes with ease.
Now coming to your problem, in your Entity class if you set some filed with Integer type then it will update your table to int4 and with allow null. The BigDecimal is also same numeric(10,2) with allow null. To avoid allow null in your database use primitive type int and double.

Custom Hibernate Id Generator for 2 Hibernate Sessions

I have a use case of dual write where I need to save entities to both Oracle and Postgres DB. I also want to implement a custom IdentifierGenerator to create new primary keys, which have to be consistent in Oracle and Postgres.
The entity is like below (Getter and Setter are ignored).
#Entity
#Table(name = "users")
#GenericGenerator(
name = "CUSTOM_ID_GENERATOR",
strategy = "<class reference>",
)
public class User implements Serializable {
#Id
#GeneratedValue( generator = "idGenerator")
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
}
When saving the entity, what I'm gonna do is like:
User user = new User();
user.setName("USER_1");
long id = oracleSession.save(user);
user.setId(id);
postgresSession.save(user);
For my code, will it cause a new id to be generated in Postgres? If so, how should the implementation be to keep the same id in both Oracle and Postgres?
Is there a way to let the IdGenerator NOT generate a new value when the id is manually set?
Thanks in advance.
#GeneratedValue will cause new value each time save method is called. So you don't have guarantee that the value would be same for Oracle and PG.
You need to use assigned Id in this case.
Your application should generate the id and should set to the user object. The assigned Id will then be saved to both the DB.
To do this you have to drop the #GeneratedValue annotation refer this documentation page.
Now the next question is how to generate unique Id for your tables, the simplest way is to use a sequence from either DB and get new value and set that to user object. Go through this question for more information on this.

checking uniq constraints without using primary key

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!

Override Hibernate Annotations

I am developing a Java Application that uses Hibernate and is connected to an Oracle instance. Another client is looking to use the same application, but requires it run on MS SQL Server. I would like to avoid making changes to the existing annotations and instead create a package of xml files that we can drop in depending on the environment.
One way to do this is using JPA XML configuration to override the existing class annotations. However, JPA does not support generic generators, which is a requirement due to the structure of our legacy database. The other way that I am looking into is to use Hibernate XML configs to remap entire classes and have access to the generator xml tag. This solution has some issues though:
Hibernate does not allow you to selectively override entity members
Hibernate does not allow you to re-map the same class (e.g. org.hibernate.AnnotationException: Use of the same entity name twice)
Does anyone have any experience with overriding annotations using Hibernate XML Configuration files or is JPA the only way to go?
Update with an Example
In Oracle, Sequences are used to generate unique IDs when inserting new records into the database. An id would then be annotated in the following manner:
#Id
#GeneratedValue(generator="EXAMPLE_ID_GEN", strategy=GenerationType.SEQUENCE)
#SequenceGenerator(name="EXAMPLE_ID_GEN", sequenceName="SEQ_EXAMPLE_ID")
#Column(name = "EXAMPLE_ID")
public String getExampleId() {
return this.exampleId;
}
However, MS SQL Server does not have the concept of Sequences (Ideological differences). Therefore, you could use a table generator to simulate sequences.
#Id
#GeneratedValue(generator="EXAMPLE_ID_GEN", strategy=GenerationType.TABLE)
#TableGenerator(name="EXAMPLE_ID_GEN", tableName="SEQUENCE", valueColumnName="VALUE", pkColumnName="SEQUENCE", pkColumnValue="EXAMPLE_ID")
public String getExampleId() {
return this.exampleId;
}
Two different configurations for two different types of databases. Keep in mind that this is a legacy database and that we aren't going to rewrite our application to support SQL Server identities, the native id generator for SQL Server (which would also require a different annotation).
To alleviate this, I have looked into using Hibernate's #GenericGenerator and point it to a class of my own creation that models org.hibernate.id.SequenceGenerator (or something similar) and also customize the structure of the table by extending org.hibernate.id.TableStructure.
Back to my original question - is any of this possible with an XML override?
How I Solved this Problem
So, in the end, I found that JPA and Hibernate did not provide the out-of-box functionality that I was looking for. Instead, I created a custom generator that checked the database dialect and set the TableStructure appropriately. As I explored all options, I ended up using Hibernate's #GenericGenerator annotation. This is an example of the Id generation annotation:
#Id
#GeneratedValue(generator="EXAMPLE_ID_GEN")
#GenericGenerator(name = "EXAMPLE_ID_GEN", strategy="com.my.package.CustomIdGenerator", parameters = {
#Parameter(name = "parameter_name", value="parameter_value")
})
public String getExampleId() {
return this.exampleId;
}
This solution necessitates that each Hibernate entity be modified with the new Id generator.
I think that if you don't use AnnotationConfiguration when configuring your SessionFactory, the annotations will be omitted.
So, use Configuration.
For your generator problem (for which the solution would normally be "use the native generator" but doesn't work for you due to working with a legacy db), you could probably extend the SQLServerDialect and override the getNativeIdentifierGeneratorClass to return a (possibly custom) generator that does what you need for your legacy db.
I have come across the need to mix-n-match legacy with new schemas/databases before in a Grails (GORM) application, which of course is running Hibernate 3 underneath.
Would not say "you're doing it wrong" - but I would keep the JPA #Annotations to the very basics like #Entity and #Column and leave it to the Hibernate dialect, which is also specified in the XML configuration file.
You might experiment with subclassing the Oracle10gDialect with one that assigns a sequence generator to all tables, versus a Sybase one which does not.
Please see this post on how to implement this.
UPDATE:
What james and I are suggesting (almost in the same minute) is to setup multiple persistence-unit sections of your persistence.xml file.
This allows one to use #Entity and #Id without supplying details in the class. The details come in the hibernate.dialect property. I suggested subclassing Oracle10gDialect (and james the SQLServerDialect) - those would do the choosing as to the table naming, id generator strategy, etc.
See --> https://forum.hibernate.org/viewtopic.php?f=1&t=993012
If you rewrite the annotations in HBM XML files, you could maintain two sets of such XML and pick which ones to use via Hibernate's mapping directives. I've done this in Hibernate Core, but not in a J2EE/JPA environment so I don't know if there are any gotchas in that respect.
The biggest downside is it likely will be a lot of work to remove all your annotations and rebuild them in XML.
I would say that if your annotations are database specific, you're doing it wrong.
In my case:
Rack and Slot are entities having custom ID Generators. I am using unidirectional one-to-one mapping. Dimension table will hold the data with a Autogenerated Custom ID as foreign key for multiple tables (Rack and Slot for example here).
And my schema looks like this : Rack ------> Dimension <-----------Slot
where Dimension will hold the data for Rack and Slot table with Generated ID.
Here the concern is that when i am saving the data like this:-
Rack rack = new Rack(params);
Dimension dim = new Dimension(params);
rack.setDimension(dim);
session.save(rack);
Data is being saved successfully with same Autogenerated ID in Rack and Dimension Tables.
But when I am saving the data for Slot table :
Slot Slot = new Slot(params);
Dimension dim = new Dimension(params);
slot.setDimension(dim);
session.save(slot);
it is showing error message as:-
attempted to assign id from null one-to-one property: rack
Can I pass the dynamic property name as "slot" when saving the data for Slot and Dimension and "rack" when saving the data for Rack and Dimension.
#GenericGenerator(name = "foreign", strategy = "foreign", parameters = {
#Parameter(name = "property", value = "slot"),
#Parameter(name = "property", value = "rack")})
Rack.java
#Entity
#Table(name="tablename")
#GenericGenerator(name = "customseq", strategy = "CustomIdGenerator")
public class Rack {
#Id
#GeneratedValue(generator = "customseq")
#Column(name = "uni_id")
private String id;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Dimension dimension;
// Getters and Setters
}
Slot.java
#Entity
#Table(name="tablename")
#GenericGenerator(name = "customseq", strategy = "CustomIdGenerator")
public class Rack {
#Id
#GeneratedValue(generator = "customseq")
#Column(name = "uni_id")
private String id;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Dimension dimension;
// Getters and Setters
}
Dimension.java
public class Dimension implements Serializable{
#Id
#Column(name = "systemid")
#GeneratedValue(generator = "foreign")
#GenericGenerator(name = "foreign", strategy = "foreign", parameters = {
#Parameter(name = "property", value = "slot"),
#Parameter(name = "property", value = "rack")})
private String systemid;
#OneToOne(mappedBy = "dimension", fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn
private Rack rack;
#OneToOne(mappedBy = "dimension", fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn
private Slot slot;
// Getters and Setters
}

Categories