I'm using Hibernate with JPA to connect to a MySql database. The database is created before the application launch so Hibernate is not building the database for me. This application is a webapp that is to be deployed to tomcat.
For one table I am using some Generics to handle some values that may either be String, Integer or Boolean. The table is created with this statement:
CREATE TABLE IF NOT EXISTS `registry` (
`DTYPE` varchar(31) NOT NULL,
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`configName` varchar(255) NOT NULL,
`label` varchar(255) NOT NULL,
`configValue` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=latin1;
The classes it is handling are an abstract class base with three classes that extends the abstract. Like this:
#Entity
#Table(name = "registry")
public abstract class Config<T> implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true, nullable = false)
private String configName;
private String label;
// standard getters and setters for the above fields.
public abstract void setConfigValue(T value);
public abstract T getConfigValue();
}
#Entity
public class BooleanConfig extends Config<Boolean>{
private Boolean configValue;
public void setConfigValue(Boolean configValue){
this.configValue = configValue;
}
public Boolean getConfigValue(){
return this.configValue;
}
}
#Entity
public StringConfig extends Config<String>{
private String configValue;
public void setConfigValue(String configValue){
this.configValue = configValue;
}
public String getConfigValue(){
return this.configValue;
}
}
#Entity
public IntegerConfig extends Config<Integer>{
// and similar as the other two but with Integer.
}
All these classes are listed in my persistence.xml and when I run the application from Eclipse for debugging, everything works as expected and the values are written and edited as I would expect them to be. My problem is once I compile the war file and upload it to Tomcat. When the webapp is deployed to Tomcat there is an error during the start up of the application caused by the database.
SchemaManagementException: Schema-validation: wrong column type encountered in column [configValue] in table [registry]; found [varchar (Types#VARCHAR)], but expecting [bit (Types#BOOLEAN)]
Now I figure the answer to the problem is that I will need to go into each of the extending classes and map the configValue column to a specific data type. My question is why do I not receive the exception when running from my IDE?
Ok I apparently fixed this problem and totally forgot that I had posted this question. So here's what I was doing wrong. The problem basically comes from the handling of the configValue of the extending classes. Each one here used its own type but the database requires that the type is varchar(255). Once I provided a Column Definition for the configValue in the form of:
#Column(columnDefinition = 'varchar(255)')
private Integer configValue; // this could be for all the types listed in the question.
the problem solved itself. As for the reason it didn't show itself while debugging in Eclipse, I'm still not sure, but this fix caused no problems during debugging and solved the problem during deployment. I didn't figure this all out myself, but I can't remember who helped me.
Related
I'm currently learning Spring-Boot and Spring-Data-JPA.
I'm using a postgresql database for storing the data.
My goal is to store ingredients with a unique and custom ID (you just type it in when creating it), but when another ingredient with the same ID gets inserted, there should be some kind of error. In my understanding, this is what happens when I use the #Id annotation, hibernate also logs the correct create table statement.
This is my Ingredient class:
public class Ingredient {
#Id
#Column(name = "ingredient_id")
private String ingredient_id;
#Column(name = "name")
private String name;
#Column(name = "curr_stock")
private double curr_stock;
#Column(name = "opt_stock")
private double opt_stock;
#Column(name = "unit")
private String unit;
#Column(name = "price_per_unit")
private double price_per_unit;
#Column(name = "supplier")
private String supplier;
-- ... getters, setters, constructors (they work fine, I can insert and get the data)
}
My controller looks like this:
#RestController
#RequestMapping(path = "api/v1/ingredient")
public class IngredientController {
private final IngredientService ingredientService;
#Autowired
public IngredientController(IngredientService ingredientService) {
this.ingredientService = ingredientService;
}
#GetMapping
public List<Ingredient> getIngredients(){
return ingredientService.getIngredients();
}
#PostMapping
public void registerNewStudent(#RequestBody Ingredient ingredient) {
ingredientService.saveIngredient(ingredient);
}
}
And my service class just uses the save() method from the JpaRepository to store new ingredients.
To this point I had the feeling, that I understood the whole thing, but when sending two post-requests to my application, each one containing an ingredient with the id "1234", and then showing all ingredients with a get request, the first ingredient just got replaced by the second one and there was no error or smth. like that in between.
Sending direct sql insert statements to the database with the same values throws an error, because the primary key constraint gets violated, just as it should be. Exactly this should have happened after the second post request (in my understanding).
What did I get wrong?
Update:
From the terminal output and the answers I got below, it is now clear, that the save() method can be understood as "insert or update if primary key is already existing".
But is there a better way around this than just error-handle every time when saving a new entry by hand?
The save method will create or update the entry if the id already exists. I'd switch to auto generating the ID when inserting, instead of manually creating the IDs. That would prevent the issue you have
When saving a new ingredient, jpa will perform an update if the value contained in the “id” field is already in the table.
A nice way through which you can achieve what you want is
ingredientRepository.findById(ingredientDTO.getIngredientId()).
ifPresentOrElse( ingredientEntity-> ResponseEntity.badRequest().build(), () -> ingredientRepository.save(ingredientDTO));
You can return an error if the entity is already in the table otherwise (empty lambda), you can save the new row
This is a downside to using CrudRepository save() on an entity where the id is set by the application.
Under the hood EntityManager.persist() will only be called if the id is null otherwise EntityManager.merge() is called.
Using the EntityManager directly gives you more fine grained control and you can call the persist method in your application when required
I have an abstract superclass which every Entity on my domain is a subclass of it.
Using DB schema generation, I want to create an Index for each Entity, on a field on the superclass, and without using the Table annotation on every subclass.
My superclass
#MappedSuperclass
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = SEQUENCE)
private Long surrogateId;
#Index(name="id_index") // Every subclass should inherit this index, with its own name
#Column(unique = true, updatable = false, nullable = false)
private UUID id = UUID.randomUUID();
An example of subclass
#Entity
public class Customer extends BaseEntity {
...
}
I tried so far:
use the Table annotation with #Index on the superclass, but Hibernate
doesn't seem to use that annotation if it is not marked with #Entity.
For example
#Table(indexes = {#Index(name="index_id", columnList = "id")})
No SQL statements are generated.
use the deprecated #Index annotation with a name "id_index", but only one index
is created on startup (the db raises an error that this index already
exists for other entities). Some generated SQL statements:
Hibernate: create index id_index on "customer" ("id")
Hibernate: create index id_index on "user" ("id")
2020-02-15 17:47:26,620 WARN o.hibernate.tool.schema.internal.ExceptionHandlerLoggedImpl - GenerationTarget encountered exception accepting command : Error executing DDL "create index id_index on "customer" ("id")" via JDBC Statement
org.postgresql.util.PSQLException: ERROR: relation "id_index" already exists
Any ideas on how to do this without too much code duplication?
Thanks
The only way I can see to do this - and it appears more trouble than it is worth to simply avoid placing an #Table annotation on each Entity - is to create a custom dialect and override the getIndexExporter() method:
public class MyPostgreSQLDialect extends PostgreSQLXXDialect{
#Override
public Exporter<Index> getIndexExporter() {
return new MyIndexExporter(this);
}
}
to return a customized Exporter, most likely extending org.hibernate.tool.schema.internal.StandardIndexExporter
public class MyIndexExporter extends StandardIndexExporter{
public MyIndexExporter(Dialect dialect){
super(dialect);
}
#Override
public String[] getSqlCreateStrings(Index index, Metadata metadata) {
//looks like you'd need to paste the whole code from superclass method
//and alter the index name accordingly
indexNameForCreation = index.getTable().getQualifiedTableName() +
"_" + index.getName();
//in the default implementation it is simply index.getName()
}
}
Alan Hay answer may work but it seems a bit of overengineering... So I decided to not use schema generation and use Liquibase instead, so I can have more control over the database.
Anyway, If anyone is having the same problem with schema generation, I tried with #Index (using eclipselink) and it worked, so this issue is only in Hibernate.
I need to reuse some specific fields in a table which is already mapped to a Hibernate entity and create a new Entity.
We've got a Table like this:
TABLE `OLDTABLE` (
/// MANY FIELDS
`aPhoneNumber` varchar(30) NOT NULL,
/// MORE FIELDS
`aActive` int(11) NOT NULL,
`aLastPaid` datetime NOT NULL,
/// EVEN MORE FIELDS
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Updates to this are currently managed with Hibernate and there is no need to change that code.
However, for reporting purposes, we need a new query which only returns some of the fields and we would like to get another Entity mapped to just these fields. Something like this:
public class NewEntity{
private String phoneNumber;
private int active;
private Date lastPaid
/// getters & setters ...
}
We want it for ease of use, instead of adding Scalars to the current queries. No updates would be necessary, just queries.
P.S. I am fairly new to Hibernate, but I got most of the basics.
UPDATE: Thank you very much for the answers. We had an emergency at work and I haven't been able to try them. I will do it this coming week as I've working non-stop on some pressing issues.
You can create a DTO instead of an entity, and use a hibernate constructor expression instead, a pseudo example
public class NewDTO{
private String phoneNumber;
private int active;
private Date lastPaid
public NewDTO(final String phoneNumber, int active, Date lastPaid) {
this.phoneNumber = phoneNumber;
this.active= active;
this.lastPaid= lastPaid;
}
/// getters & setters ...
}
and your query
select new your.package.NewDTO(e.phoneNumber, e.active, e.lastPaid) from Entity e
There is nothing wrong in creating a new entity and mapping it to already mapped database table, especially if it is a read-only entity.
I'm using WAS 8.0.0.5, which means I'm using OpenJPA 2.1.2-SNAPSHOT. I'm using the Criteria Query API and Canonical Metamodels. I need to access an Oracle View. The View has 1 column named GUID, which uses this SQL:
select sys_guid() from dual;
to populate itself.
I'm using RAD 8.5.1 and its JPA features to generate my entities based off what's in the db.
Here's my entity:
#Entity(name="vguid")
#Table(name="V_GUID")
public class VGuid implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(length=32)
private String guid;
public VGuid() {}
public String getGuid() {
return guid;
}
public void setGuid(String guid) {
this.guid = guid;
}
}
RAD is underlining #Column and providing this error:
Column "guid" cannot be resolved on table "V_GUID"
ಠ_ಠ
I know that #Column(length=32) works because I use it works within another Entity that reads a GUID from an Oracle table (The View is used to populate the GUID field of this other table).
How can I resolve this error?
O.K....so, I closed RAD and reopened the project. The error disappeared. Arghhh!
We use annotations for mapping the entity class with the database table by simply specifying #Entity and more like #Id, table joins and many things. I do not know how these entity variables are getting mapped with database table. Can anyone give a short description for understanding.
Thanks :)
Well the idea is to translate your objects and their connections with other objects into a relational database. These two ways of representing data (objects defined by classes and in tables in a database) are not directly compatible and that is where a so called Object Relational Mapper framework comes into play.
So a class like
class MyObject
{
private String name;
private int age;
private String password;
// Getters and setters
}
Will translate into a database table containing a column name which is of type varchar, age of type int and password of type varchar.
Annotations in Java simply add additional information (so called meta data) to your class definitions, which can be read by any other class (e.g. JavaDoc) and in the case of the Java Persistence API will be used by an ORM framework like Hibernate to read additional information you need to translate your object into the database (your database table needs a primary id and some information - like what type of a relation an object has to another - can't be automatically determined by just looking at your class definition).
Annotations are very well explained here:
http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/
annotations are just metadata on a class, nothing magical. You can write your own annotations. Those annotations are given retention policies of runtime (which means you have access to that metadata at runtime). When you call persist etc the persistence provider iterates through the fields (java.lang.reflect.Field) in your class and checks what annotations are present to build up your SQL statement. Try writing your own annotation and doing something with it. It won't seem very magical after that.
in your case annotation working means mapping with tablename with entity class is look like as ....
#Entity
#Table(name = "CompanyUser")
public class CompanyUserCAB implements java.io.Serializable
{
private long companyUserID;
private int companyID;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "companyUserID")
public long getCompanyUserID()
{
return this.companyUserID;
}
public void setCompanyUserID(long companyUserID)
{
this.companyUserID = companyUserID;
}
#Column(name = "companyID")
public int getCompanyID()
{
return this.companyID;
}
public void setCompanyID(int companyID)
{
this.companyID = companyID;
}
}