I have a Java Spring MVC app using Hibernate and a MySQL 5.7 DB. Hibernate is configured to automatically update the schema from new entities via: <prop key="hibernate.hbm2ddl.auto">update</prop>
It is working for all the entities in my app except for one:
#Entity
#IdClass(QuestionId.class)
#Getter
#Setter
#Accessors(chain = true)
#EqualsAndHashCode
public class Question {
#Id
private String key;
#Id
private Long version;
#Type(type = "json")
#Column(columnDefinition = "json")
private JsonNode metaData;
}
This entity is defined in exactly the same way as other entities, but when the app starts, this particular table does not get created in the DB. There are no error messages given that I have been able to uncover.
I actually discovered the problem with help from an answer to a different question: hibernate not creating table but no error messages
This behavior can happen when using reserved db keywords as property/column names. In my case, key is a reserved keyword in MySQL: https://dev.mysql.com/doc/refman/8.0/en/keywords.html
public class Question {
#Id
private String key; // <==== NO; `KEY` is a db reserved keyword.
#Id
private Long version;
#Type(type = "json")
#Column(columnDefinition = "json")
private JsonNode metaData;
}
Changing the property name (or alternatively specifying an explicit column name) fixes the issue. There may have been an error message if I enabled the right debugging flags, but the problem was solved before then.
Related
I am using following code to define MyEntity,
#Entity
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "MY_TABLE_ID")
private Integer myTableId;
#Column(name = "MY_TABLE_NM")
private String myTableName;
//Getters Setters
}
For the first POST after my application starts, I create MyEntity everything works fine, MY_TABLE_ID starts with 1 and works as expected.
My issue is, If somebody inserts data manually before I do my POST then I get duplicate key exception as myTableId is entered as 1 which is already present.
My main problem is I can't create database sequence for using GenerationType.SEQUENCE now to resolve this as database can't be altered now.
I have tried various combinations of GenerationType, TableGenerator but I am unable to successfully tackle it.
Setting initialValue to some larger number to avoid duplicate values can temporarily resolve my problem but I am unable to do it too.
If someone can help me with initialValue with AUTO or give me some other better solution without database changes will be great :)
As MY_TABLE_ID is an identity column, following annotations will work.
#Entity
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY) // <-- IDENTITY instead of AUTO
#Column(name = "MY_TABLE_ID")
private Integer myTableId;
#Column(name = "MY_TABLE_NM")
private String myTableName;
//Getters Setters
}
The identity column will automatically assign an value as soon as the transaction is committed. You are not to set any values for an identity column, as its the job of the database to assign the values. Therefore you also don't need to think about any initial values (forget them completely for identity columns)
I tried various options in answers provided here and for similar questions on stackoverflow and other forums,
I had few limitations,
I couldn't create database sequence as my database changes were freezed.
I didn't want to introduce new Custom IdGenerator class because it would add confusion to other people working with me.
It was resolved using following change:
Adding GenericGenerator with increment strategy helped me, I made following changes to my code.
#Entity
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator="seq")
#GenericGenerator(name = "seq", strategy="increment")
#Column(name = "MY_TABLE_ID")
private Integer myTableId;
#Column(name = "MY_TABLE_NM")
private String myTableName;
//Getters Setters
}
It helped me because,
From Hiberbate DOCs
increment
An IdentifierGenerator that returns a long, constructed by counting
from the maximum primary key value at startup. Not safe for use in a
cluster!
Since, it was incrementing already existing myTableId even if it was manually inserted, this resolved my issue.
You can also implement your own generator if you need more control.
See this interface IdentifierGenerator.
So you can get the count of records, for example through a #NamedQuery.
Then you can generate an identifier yourself.
public class MyEntityKeyGenerator implements IdentifierGenerator {
#Override
public Serializable generate(SessionImplementor session, Object object) {
// SELECT count(ent) from MyEntity ent;
Long count = (Long) session.getNamedQuery("count-query").uniqueResult();
// calc and return id value
}
}
Entity:
class MyEntity {
#Id
#GenericGenerator(name = "my_generator",
strategy = "org.common.MyEntityKeyGenerator")
#GeneratedValue(generator = "my_generator")
private Long id;...
Just do not forget about the lock.
I use the generation type Identity, which basically means that the db, takes care of Id generation.
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#MappedSuperclass
#EntityListeners(EntityListener.class)
#EqualsAndHashCode(of = {"id", "createdAt"})
public abstract class AbstractEntity<ID extends Serializable> implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private ID id;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "CREATED_AT", updatable = false)
private Date createdAt;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "UPDATED_AT")
private Date updatedAt;
}
You can also use, Sequence generation:
#Entity
#SequenceGenerator(name="seq", initialValue=1, allocationSize=100)
public class EntityWithSequenceId {
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq")
#Id long id;
}
I have been using Hibernate a lot but using Hibernate/JPA with UUID got me stumped a bit. I am using hibernate 5.2.12.Final.
I have an object called TimePeriod with this mapping:
#Entity(name = "time_period")
public class TimePeriod extends AbstractDomainObject {
#OneToMany(mappedBy = "timePeriod", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<TimePeriodBlock> timePeriodBlocks = new ArrayList<>();
...
public void addTimePeriodBlock(TimePeriodBlock timePeriodBlock) {
timePeriodBlock.setTimePeriod(this);
this.timePeriodBlocks.add(timePeriodBlock);
}
...
With the following child relationship:
#Entity(name = "time_period_block")
public class TimePeriodBlock extends AbstractDomainObject {
...
#ManyToOne
#JoinColumn(name = "time_period_id", nullable = false)
private TimePeriod timePeriod;
...
They share this super class:
#MappedSuperclass
public abstract class AbstractDomainObject {
...
#Id
#GeneratedValue
#Column(name = "id", columnDefinition = "uuid", updatable = false)
private UUID id;
...
When I execute the following:
// pseudo code
TimePeriod t = new TimePeriod();
t.setName("test");
TimePeriodBlock b = new TimePeriodBlock();
t.addTimePeriodBlock(b);
em.persist(t);
I get the exception:
...
Caused by: org.hibernate.PropertyValueException: not-null property references a null or transient value : test.TimePeriodBlock.timePeriod
...
Some notes:
I strongly believe that this could be because Hibernate generates the UUID (and not the database) but, since I am not sure, I hope some fellow Developer might know how this could work.
I am using PostgreSQL 9.6 and the database can also generates UUIDv4 but requires compiling an extra extension so I opted for Hibernate to generate it.
When I enter some data in the database and retrieve the data it is fetched without any error.
Storing other objects without #ManyToOne relationships do store without any error and have a UUID that is generated by Hibernate.
Well after some debugging and using Luay Abdulreheem suggestion I found out that hibernate is working just fine; in this case my objects are send using a REST interface (using Jackson) and the reference to the parent was lost as the unmarshalling of the JSON is done using fields.
So nothing to see here, move along...
I am trying to save a byte[] field to local filesystem instead of the database.
I have tried the JPA annotation #Transient
#Entity
#Table(name = "screenshot")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Screenshot implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#Lob
#Transient
#Column(name = "image", nullable = false)
private byte[] image;
#Column(name = "otherField", nullable = false)
private String otherField;
#Column(name = "otherField2", nullable = false)
private String otherField2;
}
But after I persist the entity, for e.g. The returnedEntity image property will not be returned due to #Transient annotation.
Screenshot returnedEntity = screenshotRepository.save(entity);
But I need to persist it in the database first in-order to get an unique ID and use this ID as part of the file path to persist only the image(binary) field in my local filesystem.
I run into a situation that before I save the entity into the database, I do not have an unique ID. But after I save the entity, I lost the binary byte[] data to save the file in the local filesystem.
Can't seems to figure a good way to link the saved ID to the binary byte[].
Your problem is that you want the field to be Transiented and Included (serialized) at the sametime. Some people suggest to use #JsonInclude annotation, like what is mentioned here.
However, from my point of view, the idea is in the logic of your app and its not related to JPA. So, I would copy the object before presisting it, or at least copy the image field alone. After presistance, assigning the ID of the presisted object to the ID of the copied object.
Or, another solution, you can add a string field to your database that will hold a path to your image; You start by writing the image to the 'heart content' or wherever, and then assign the path of the image to the entity and persist that path in the database.
Or, another solution, you can add another data model layer to your system above the entity layer, which contains the JSON classes, so you dont need to have a transient fields in your entity nor JSON annotations.
I am getting an error when using an Oracle DB and Spring Data. The error is:
ORA-00942: table or view does not exist
The cause of this error is that the user I am connecting with does not have access to the tables in the schemas I wish to connect to.
I read that 2 fixes to this are to create synonyms in my database or to specify the schema that each entity/table belongs to.
I am going to try the Schema approach first. How do I do so?
My example entity below, a Dog in the Vet Schema:
#Entity
#Table(name = "Dog")
public class Dog
{
#Id
private String id;
#Column(name = "NAME")
private String name;
#Column(name = "Owner")
private String owner;
//getters and setters etc...
The #Table annotation provides the schema attribute:
#Table(name = "Dog", schema = "Vet")
You must prefix your tables with the schema name and with a . inbetween them:
#Table(name = "VET.Dog")
I have the following object structure:
#Document(collection = "user")
#TypeAlias("user")
public class User {
#Id
private ObjectId id;
private Contact info = new Contact();
}
and here is the Contact pojo:
public class Contact {
#Indexed(unique = true)
private String mail;
}
But for some reasons not known to me, I don't see Spring-data creating a unique index for the property info.mail
To summarize, I have this json structure of user object:
{_id:xxxxx,info:{mail:"abc#xyz.shoes"}}
And I want to create a unique index on info.mail using Spring data with the above pojo structure. Please help.
As far as I remember, annotating embedded fields with #Indexed will not work. #CompoundIndex is the way to go:
#Document(collection = "user")
#TypeAlias("user")
#CompoundIndexes({
#CompoundIndex(name = "contact_email", def = "{ 'contact.mail': 1 }", unique = true)
})
public class User {
#Id
private ObjectId id;
private Contact info = new Contact();
}
In my case I had a fresh spring boot application 2.3.0 with just #Document, #Id and #Indexed annotations. I was able to retrieve and insert documents but it refused to create the index other than the PK. Finally I figured that there is a property that you need to enable.
spring.data.mongodb.auto-index-creation = true
As a matter of fact it even works on nested objects without #Document annotation.
Hope this helps :)
Obsolete answer, this was with and older version of mongodb 1.x.
Had the same issue, it seems that your Contact class is missing the #Document annotation i.e.
#Document
public class Contact {
#Indexed(unique = true)
private String mail;
}
Should work, quote from the spring mongodb reference
Automatic index creation is only done for types annotated with #Document.
Extending #Xenobius's answer:
If any configuration extending AbstractMongoClientConfiguration is set, MongoMappingContext will back off. The result is:
spring.data.mongodb.auto-index-creation = true will not be effective
You will need add this into your own configuration:
#Override
protected boolean autoIndexCreation() {
return true;
}
ref: https://github.com/spring-projects/spring-boot/issues/28478#issuecomment-954627106