I'm using Spring-boot 1.4.1.RELEASE, with Spring Data and Hibernate to persist some data into a MySQL database.
I have this class, Respondent, annotated with #Entity and one of the fields annotated as below:
#Column(name = "firstName", nullable = false, length = 100)
private String firstName;
When I try to save a Respondent to the DB by calling save() on its CrudRepository<Respondent, Long>, I get this error:
ERRORcom.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'first_name' in 'field list'
This error had started occurring before I had the #Column annotation for the field, so I thought it was some default Hibernate behaviour to map firstName to first_name, but I've added the #Column annotation to it and nothing changed. Is it still wrong? I've already rebuilt the application with mvn clean package.
Here's my Respondent entity:
#Entity
public class Respondent {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name = "firstName", nullable = false, length = 100)
private String firstName;
private String tussenvoegsel;
#Column(name = "lastName", nullable = false, length = 100)
private String lastName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Company_id", nullable = false)
private Company company;
By default Spring uses jpa.SpringNamingStrategy to generate the table names.
The ImprovedNamingStrategy will convert CamelCase to SNAKE_CASE where as the EJB3NamingStrategy just uses the table name unchanged.
You can try to change the naming_strategy to:
spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.EJB3NamingStrategy
or the #Column name attribute should be in lowercase #Column(name = "firstname")
For Hibernate 5 this should work (I am not quite sure if you also need the above one. But try it with both):
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
There we need to follow some naming strategy.
The column name should be in lowercase or uppercase.
#Column(name="FIRSTNAME")
private String firstName;
or
#Column(name="firstname")
private String firstName;
keep in mind that, if you have your column name "first_name" format in the database then you have to follow the following way
#Column(name="firstName")
private String testName;
Related
I have a user that can have a wallet. Now, when user create a wallet I want to give him a option to create a transaction on that wallet. So, on that form I would like to have next fields, so prompt user to insert:
Amount of transaction, Date, note of transaction, category of transaction
So far I have this in Spring:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "transaction_id")
private Integer id;
private double amount;
private String note;
#DateTimeFormat(pattern = "dd-MM-yyyy")
#Column(name = "date")
private Date date;
But I have problem with field category. I want to prompt user to pick from dropdown menu one of category from the list. But how to create a field categories that will be filled with names of categories?
I tried with:
#Column(name = "categories")
private List categories;
But I'm getting:
Could not determine type for: java.util.List, at table: transaction, for columns: [org.hibernate.mapping.Column(categories)]
Spring JPA lets you handle this a couple of different ways. Since you didn't specify what type of thing a category is, I am assuming you want it to be a String.
#ElementCollection
The first, easiest, and generally recommended way is to use the #ElementCollection. If your categories are fairly well fixed, you can even use enums for this.
#ElementCollection
private List<String> categories=new ArrayList<>();
With this, JPA will generate a second table in the database called transaction_categories You don't have to create an Entity for this table or anything. Everything is handled under the covers.
JsonB
If you are using postgres 10+ (I think) you can make a column into a JSON object. You will need the following dependency in your gradle.
implementation 'com.vladmihalcea:hibernate-types-52:2.15.1'
And you will need to change your model thus:
#TypeDefs({
#TypeDef(name = "json", typeClass = JsonType.class)
})
#Entity
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "transaction_id")
private Integer id;
private double amount;
private String note;
#DateTimeFormat(pattern = "dd-MM-yyyy")
#Column(name = "date")
private Date date;
#Type(type = "json")
#Column(columnDefinition = "jsonb")
private List<String> categories=new ArrayList<>();
}
So long as this list does not become gigantic, it is a pretty decent solution.
I have an entity in containing :
#Entity
#Table(name = "pictures")
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Getter
#Setter
public class PictureEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", insertable = false, updatable = false, nullable = false)
private UUID id;
#Column
private String path;
#Column(name = "thumb_path")
private String thumbPath;
#Column
#Enumerated(EnumType.STRING)
private Status status;
#Column(name = "creation_utc")
#Temporal(TemporalType.TIMESTAMP)
private Date creationTimeUtc;
#Column(name = "creation_local")
#Temporal(TemporalType.TIMESTAMP)
private Date creationTimeLocal;
#ManyToOne
#JoinColumn(name = "project_id", updatable = true, insertable = true)
private ProjectEntity project;
#ManyToOne
#JoinColumn(name = "user_id", updatable = true, insertable = true)
private UserEntity user;
#OneToOne(mappedBy = "picture", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private ProcessedPictureEntity processedPicture;
public enum Status {
VALIDATED,
PROCESSED,
REJECTED,
WAITING_VALIDATION
}
}
When I call a save with H2 database, it saves the "project_id" field too.
But if I use mysql, the generated query isn't the same, project is not saved (which I think is the correct behavior).
I want the test with H2 to crash if updatable/insertable on project_id are false.
How can I correct this ?
If I understand you correctly you have two problems:
H2 and MySQL behave differently causing bugs to slip through your tests.
You want to test if a certain field got updated.
For 1.: I recommend Testcontainers. It allows you to run tests with an actual MySQL database (or any other database that you can get a docker image for).
This will make your integration tests way more valuable.
For 2.: Execute whatever code you suspect does the update in question and then check if the field got changed.
Make sure the changes get flushed which is a common cause of tests not behaving as on things.
For checking for changes I recommend Springs JdbcTemplate for easily executing queries.
Say I have a class that holds user info
User.java
#Data
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
//User Data
#Column(name = "first_name", nullable = false)
private String firstName;
#Column(name = "last_name", nullable = false)
private String lastName;
}
In front end page admin can update Bio Dynamically by defining new field. Say clicking on + button he can add new field called middle name, age or address, etc.
P.S. It is kind of admin privilege and number of updates runtime will be limited and hence no issue of creating unlimited fields.
How can I address this dynamic addition of entity in MySQL using Spring Boot?
You might add new fields using a custom map, for example:
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "custom_fields")
#MapKeyColumn(name = "field")
private Map<String, String> customFields;
I have One-To-Many relationship, here is my code
#Entity
#Table(name = "catalog")
public class Catalog {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "catalog_id")
private int catalog_id;
#NotEmpty
#Size(min = 3, max = 255)
#Column(name = "name", nullable = false)
private String name;
#OneToMany(mappedBy="mycatalogorder")
private List<Order> orders;
#OneToMany(mappedBy="mycatalog")
private List<CatalogItem> items;
// setters and getters
}
#Entity
#Table(name = "catalogitem")
public class CatalogItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "catalogitem_id")
private int catalogitem_id;
#NotEmpty
#Size(min = 3, max = 255)
#Column(name = "name", nullable = false)
private String name;
#NotEmpty
#Column(name = "price", nullable = false)
private Double price;
#OneToOne(mappedBy="ordercatalogitem", cascade=CascadeType.ALL)
private OrderItem morderitem;
#ManyToOne
#JoinColumn(name="catalog_id", nullable=false)
private Catalog mycatalog;
// setters and getters
}
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "order_id")
private int order_id;
#NotEmpty
#Size(min = 3, max = 255)
#Column(name = "name", nullable = false)
private String name;
#NotEmpty
#Size(min = 3, max = 1024)
#Column(name = "note", nullable = false)
private String note;
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(pattern = "ddmmYYYY HH:mm:ss")
#Column(name = "created", nullable = false)
private Date created;
#OneToMany(mappedBy="myorder")
private Set<OrderItem> orderItems;
#ManyToOne
#JoinColumn(name="catalog_id", nullable=false)
private Catalog mycatalogorder;
#PrePersist
protected void onCreate() {
created = new Date();
}
// setters and getters
}
#Entity
#Table(name = "orderitem")
public class OrderItem {
#Id
#Column(name="catalogitem_id", unique=true, nullable=false)
#GeneratedValue(generator="gen")
#GenericGenerator(name="gen", strategy="foreign", parameters=#Parameter(name="property", value="catalogitem"))
private int catalogitem_id;
#Column(name = "quantity")
private int quantity;
#OneToOne
#PrimaryKeyJoinColumn
private CatalogItem ordercatalogitem;
#ManyToOne
#JoinColumn(name="order_id", nullable=false)
private Order myorder;
// setters and getters
}
And I am getting the exception:
org.springframework.http.converter.HttpMessageNotWritableException:
Could not write content: failed to lazily initialize a collection of
role: com.example.helios.model.Catalog.items, could not initialize
proxy - no Session; nested exception is
com.fasterxml.jackson.databind.JsonMappingException: failed to lazily
initialize a collection of role:
com.example.helios.model.Catalog.items, could not initialize proxy -
no Session
org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:271)
org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:100)
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:222)
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:183)
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:80)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:126)
My versions is:
SpringFramework 4.2.4.RELEASE
Hibernate 4.3.11.Final
Jackson 2.7.4
Jacksontype 2.7.1
This is the normal Hibernate behaviour
In one to many relations, hibernate loads the father entity (Catalog in your case) but it will load the children entities List (List items and List orders in your case) in a LAZY mode
This means you can't access to these objects because they are just proxies and not real objects
This is usefull in order to avoid to load the full DB when you execute a query
You have 2 solution:
Load children entities in EAGER mode (I strongly suggest to you to not do it because you can load the full DB.... but it is something related to your scenario
You don't serialize in your JSON the children entities by using the com.fasterxml.jackson.annotation.JsonIgnore property
Angelo
A third option which can be useful if you don't want to use EAGER mode and load up everything is to use Hibernate::initialize and only load what you need.
Session session = sessionFactory.openSession();
Catalog catalog = (Catalog) session.load(Catalog.class, catalogId);
Hibernate.initialize(shelf);
More information
I had the same problem but a fixed by:
#OneToMany
#JoinColumn(name = "assigned_ingredient", referencedColumnName = "ingredient_id")
#Fetch(FetchMode.JOIN) // Changing the fetch profile you can solve the problem
#Where(clause = "active_ind = 'Y'")
#OrderBy(clause = "meal_id ASC")
private List<Well> ingredients;
you can have more information here: https://vladmihalcea.com/the-best-way-to-handle-the-lazyinitializationexception/
It's caused by an infinite loop when parsing datas to JSON.
You can solve this by using #JsonManagedReference and #JsonBackReference annotations.
Definitions from API :
JsonManagedReference (https://fasterxml.github.io/jackson-annotations/javadoc/2.5/com/fasterxml/jackson/annotation/JsonManagedReference.html) :
Annotation used to indicate that annotated property is part of two-way
linkage between fields; and that its role is "parent" (or "forward")
link. Value type (class) of property must have a single compatible
property annotated with JsonBackReference. Linkage is handled such
that the property annotated with this annotation is handled normally
(serialized normally, no special handling for deserialization); it is
the matching back reference that requires special handling
JsonBackReference: (https://fasterxml.github.io/jackson-annotations/javadoc/2.5/com/fasterxml/jackson/annotation/JsonBackReference.html):
Annotation used to indicate that associated property is part of
two-way linkage between fields; and that its role is "child" (or
"back") link. Value type of the property must be a bean: it can not be
a Collection, Map, Array or enumeration. Linkage is handled such that
the property annotated with this annotation is not serialized; and
during deserialization, its value is set to instance that has the
"managed" (forward) link.
Example:
Owner.java:
#JsonManagedReference
#OneToMany(mappedBy = "owner", fetch = FetchType.EAGER)
Set<Car> cars;
Car.java:
#JsonBackReference
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "owner_id")
private Owner owner;
Another solution is to use #JsonIgnore which will just set null to the field.
Here is my solution for this task with Hibernate. I marked hibernate releation with #JsonIgnore and use custom field for jackson, in which I check if the field is loaded. If you need serialize collection to json then you should manualy call collection getter during hibernate transaciton.
#JsonIgnore
#OneToMany(mappedBy = "myorder")
private List<OrderItem> orderItems = new ArrayList<>();
#JsonProperty(value = "order_items", access = JsonProperty.Access.READ_ONLY)
private List<OrderItem> getOrderItemsList() {
if(Hibernate.isInitialized(this.relatedDictionary)){
return this.relatedDictionary;
} else{
return new ArrayList<>();
}
}
#JsonProperty(value = "order_items", access = JsonProperty.Access.WRITE_ONLY)
private void setOrderItemsList(List<OrderItem> orderItems) {
this.orderItems = orderItems;
}
I know this is an old post but this might still help someone facing a similar issue. To solve the problem, iterate through the list of items and set the lazy-loadable collection to null. Then set your mapper to include NON-NULL
for (Catalog c : allCatalogs) {
c.setItems(null);
}
objectMapper.setSerializationInclusion(Include.NON_NULL)
Using FetchType.LAZY , if still getting the error "Could not write content: failed to lazily initialize a collection of role" , that may be probably caused by somewhere in the logic (perhaps in a controller) , Catalog is being tried to be deserialized that contains list of catalog items which is a proxy but the transaction has already ended to get that.
So create a new model ('CatalogResource' similar to catalog but without the list of items).
Then create a catalogResource object out of the Catalog (which is returned from the query)
public class CatalogResource {
private int catalog_id;
private String name;
private List<Order> orders;
}
I think the best solution to your problem (which also is the simplest) is to set your FetchType to LAZY and simply annotate the oneToMany collection fields using #transient.
Setting FetchType to EAGER isn't a good idea most times.
Best of luck.
"You don't serialize in your JSON the children entities by using the com.fasterxml.jackson.annotation.JsonIgnore property"
Add #JsonIgnore for hibernate lazy loading properties eg. #ManyToOne. That should work
I have two classes: Event and User. Every 'event' is created by only one 'user', but a 'user' can create many 'events'. I created relationships like this:
#Entity
#Table(name="events")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Event {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#NotBlank
private String name;
#NotBlank
#Type(type="text")
private String description;
private String event_type;
#NotNull
#Type(type="org.jadira.usertype.dateandtime.joda.PersistentLocalDateTime")
private LocalDateTime expected_start;
#Type(type="org.jadira.usertype.dateandtime.joda.PersistentLocalDateTime")
private LocalDateTime expected_end;
#Type(type="org.jadira.usertype.dateandtime.joda.PersistentLocalDateTime")
private LocalDateTime actual_start;
#Type(type="org.jadira.usertype.dateandtime.joda.PersistentLocalDateTime")
private LocalDateTime actual_end;
#NotBlank
private String environment;
private int executed_by;
#Temporal(TemporalType.TIMESTAMP)
#CreationTimestamp
private Date created_at;
#Temporal(TemporalType.TIMESTAMP)
#UpdateTimestamp
private Date updated_at;
#ManyToOne
#JoinColumn(name="created_by")
private User creator;
}
#Entity
#Table(name="users")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "username")
#FieldMatch(first = "password", second = "repassword",message = "The password fields must match")
public class User{
#Id
#NotBlank
#Size(min=5,max=15)
#Column(name="username", unique=true)
private String username;
#NotBlank
private String first_name;
#NotBlank
private String last_name;
#NotBlank
private String password;
#NotBlank
#Transient
private String repassword;
#NotBlank
private String email;
#NotBlank
private String phone;
#Temporal(TemporalType.TIMESTAMP)
#CreationTimestamp
private Date created_at;
#Temporal(TemporalType.TIMESTAMP)
#UpdateTimestamp
private Date updated_at;
#OneToMany(mappedBy="creator")
private Collection<Event> events=new ArrayList<Event>();
}
DAO:
public List<Event> getEvents() {
Criteria criteria=getCurrentSession().createCriteria(Event.class);
return (List<Event>) criteria.list();
}
I am using jackson for converting to JSON. when make an ajax call for a particular event, it pull the user information along with it. that's OK as it is need for me. But it also pull's other events created by user because of #oneToMany in user class. How to overcome this
I guess that Jackson, while serializing, checks the events collection size and, since you are using openSessionInViewFilter, the collection gets fetched, so it puts it in the response. You can just #JsonIgnore the events field.
Design note:
I think that "Open session in view" is an anti-pattern. Any transaction management should be done a level below the view layer. In order to decouple view layer from the layers below you should be returning DTOs to the view layer instead of JPA entities. Unless you're doing some toy project, that's the investment you won't regret.
How I do it:
I use select new mechanism from JPA to write dedicated query
to make queries typesafe I use QueryDSL (but that's optional, you can try out an ordinary JPQL first)
queries return DTO objects that don't even need Jackson annotations as they represent what I want to get in the view.
This approach has additional advantage of being much faster than normal entity loading by JPA. Some prefer loading entities and then repackage them which saves effort required to write the query (which is minimal once you get it how it works) but then there is no performance benefit.