I work with a Spring boot application and this is the entity class I work,
#Entity
public class User {
// form:hidden - hidden value
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// form:input - textbox
#Column(name = "name", columnDefinition = "VARCHAR(30)", nullable = false)
String name;
// form:input - textbox
#Column(name = "email", columnDefinition = "VARCHAR(50)", nullable = false)
String email;
// form:input - password
#Column(name = "password", columnDefinition = "VARCHAR(20)", nullable = false)
String password;
// form:textarea - textarea
#Column(name = "address", columnDefinition = "VARCHAR(255)")
String address;
// form:input - password
String confirmPassword;
// form:checkbox - single checkbox
#Column(name = "newsletter")
boolean newsletter;
// form:radiobutton - radio button
#Column(name = "sex", columnDefinition = "VARCHAR(1)")
String sex;
// form:radiobuttons - radio button
#Column(name = "number")
Integer number;
// form:select - form:option - dropdown - single select
#Column(name = "country", columnDefinition = "VARCHAR(10)")
String country;
// form:checkboxes - multiple checkboxes
#ElementCollection
#NotNull
List<String> framework;
// form:select - multiple=true - dropdown - multiple select
#ElementCollection
List<String> skill;
}
I intend to only create the following columns in the user table,
id,
name,
email,
address,
password,
newsletter,
framework,
sex,
Number,
Country,
Skill
For example, the corresponding SQL will be like (though I don't want to write explicitly),
CREATE TABLE user (
id LONG GENERATED BY DEFAULT AS IDENTITY(START WITH 1, INCREMENT BY 1) PRIMARY KEY,
name VARCHAR(30),
email VARCHAR(50),
address VARCHAR(255),
password VARCHAR(20),
newsletter BOOLEAN,
framework VARCHAR(500),
sex VARCHAR(1),
Number INTEGER,
Country VARCHAR(10),
Skill VARCHAR(500)
);
The starting controller class,
#Controller
public class UserController {
private final Logger logger = LoggerFactory.getLogger(UserController.class);
#Autowired
private UserService userService;
private static List<User> populateDefaultUserValues() {
List<User> users = new ArrayList<>();
User user = new User();
user.setName("Ella");
user.setEmail("xyz#gmail.com");
user.setPassword("df32d343H");
user.setFramework(Arrays.asList("Spring MVC, GWT".split("\\s*,\\s*")));
users.add(user);
user = new User();
user.setName("Alex");
user.setEmail("alex#hotmail.com");
user.setPassword("12HH2d343H");
user.setFramework(Arrays.asList("Spring MVC, GWT".split("\\s*,\\s*")));
users.add(user);
user = new User();
user.setName("Romanna");
user.setEmail("romanna#hotmail.com");
user.setPassword("Rommann343");
user.setFramework(Arrays.asList("Spring MVC, GWT".split("\\s*,\\s*")));
users.add(user);
return users;
}
#GetMapping(value = "/")
public String index() {
return "redirect:/users";
}
#GetMapping(value = "/users")
public String showAllUsers(Model model) {
List<User> users = populateDefaultUserValues();
users.forEach(user -> {
userService.save(user);
});
model.addAttribute("users", userService.findAll());
return "list";
}
}
When I run the app, I see that this created in the MYSQL database,
How do I omit the confirmed_password field and add the columns of skill and framework? I expect them to be VARCHAR?
How do I omit the confirmed_password field
You should use the #Transient annotation.
add the columns of skill and framework?
Since you've marked them as #ElementCollection - there will be no columns in user table, as you can't persist a collection of values in a single relation attribute (it would violate 1NF restrictions). There are two additional tables, named like user_framework and user_skill, they are related to user table with user_id columns.
You may read the Java Persistense wiki and the Hibernate documentation about collections to find different examples of proper usage. Although, your code is fine and, in common case, you should not worry about database tables, relations and data consistency - ORM framework (Hibernate, by default) will handle all these things for you.
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 am developing a spring boot project, linked to a MySQL database through Hibernate.
Everything runs fine when the database starts empty. However, when the dummy data data.sql file runs before lauching the application, and then inserting objects through the application, the insertion doesn't take into account previous IDs, therefore resulting in duplicate entries until the number of lines is reached :
I'll try explaining better with an example :
At application start, the data.sql file inserts 3 dummy users.
Reaching registration page to add a new user and submitting, Hibernate returns an error 'Duplicate entry '1' for PRIMARY key', then 'Duplicate entry '2' for PRIMARY key' and 'Duplicate entry '3' for PRIMARY key' after retrying two times.
At the fourth retry, the user is added.
The ID therefore does auto-increment, but doesn't take into account previously inserted rows through data.sql.
Note that I've tried playing around with the generation type (AUTO / IDENTITY) of the User class with no success, and my hibernate datasource is on create-drop mode.
Update :
User entity :
#Data
#Builder
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "users")
public class User {
public final static Role DEFAULT_ROLE = new Role();
static {
DEFAULT_ROLE.setId(2);
DEFAULT_ROLE.setRole("NORMAL");
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private int id;
#Column(name = "user_name")
#Length(min = 5, message = "*Your user name must have at least 5 characters")
#NotEmpty(message = "*Please provide a user name")
private String userName;
#Column(name = "email")
#Email(message = "*Please provide a valid Email")
#NotEmpty(message = "*Please provide an email")
private String email;
#Column(name = "password")
#Length(min = 5, message = "*Your password must have at least 5 characters")
#NotEmpty(message = "*Please provide your password")
private String password;
#Column(name = "name")
#NotEmpty(message = "*Please provide your name")
private String name;
#Column(name = "last_name")
#NotEmpty(message = "*Please provide your last name")
private String lastName;
#Column(name = "active")
private Boolean active;
#ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
#Column(name = "phone_number")
#NotEmpty(message = "*Please provide a phone number")
private String phoneNumber;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "address_id", referencedColumnName = "address_id")
private Address address;
}
Any idea what would solve it? :)
Cheers.
The problem is not in Hibernate, but in the statements in data.sql.
In those INSERT statements your probably give explicit values for the user_id column, hence MySQL does not increment its AUTO_INCREMENT counter. Hibernate's default generation strategy for MySQL is IDENTITY, which relies on the AUTO_INCREMENT feature.
The easiest solution is to omit the users_id column in the INSERT statements. You can however also set the initial AUTO_INCREMENT value for the table to a higher number:
ALTER TABLE users AUTO_INCREMENT=1001
Edit: Your identity field is declared as int id, therefore it can never be null. Change it to Integer, so that Hibernate can distinguish between persisted (id != null) and not persisted entities.
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'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;
I've got two entity objects in my database: UserEntity and ItemEntity and they're mapped with OneToMany relationship.
Here is my code:
UserEntity:
#Entity
#Table(name = "users")
public class UserEntity {
#Id
#Column(name = "user_id")
#GeneratedValue
public int user_id;
#Column(name = "userlogin")
public String userlogin;
#Column(name = "userpass")
public String userpass;
#Column(name = "name")
public String name;
#Column(name = "email")
public String email;
....
#JsonBackReference
#OneToMany(mappedBy="user", cascade = { CascadeType.MERGE },fetch=FetchType.EAGER)
private List<ItemEntity> items;
ItemEntity:
#Entity
#Table(name = "items")
public class ItemEntity {
#Id
#Column(name = "id")
#GeneratedValue
private int id;
#Column(name = "title")
public String title;
#Column(name = "info")
public String info;
#JsonManagedReference
#ManyToOne
#JoinColumn(name="user_id")
private UserEntity user;
And now I'm trying to read all my Items from my database with specific fields from users that owns current item. I need only UserEntity name and email.
This code:
Query query = this.sessionFactory.getCurrentSession().createQuery("from ItemEntity WHERE title = :title");
returns all fields from UserEntity also, because it's mapped, but I don't want that, because I'm sending that data as JSON, and someone can see all informations about user who own that item (like user login and password) in some dev tools like Chrome.
How to reach that?
I'd suggest you use DTO.
Covert your entities to DTO and then transform the DTO objects to
json string.
In the DTO populate only those field that you want as part of your response.
This would make your design more clean.
In addition to what's jitsonfire is suggesting, you can write a query like this
select name, email from ItemEntity WHERE title = :title
than get your results like
List<Object[]> result = query.list();
The object array will contain your columns, the list element will equal to rows, so you can do something like
for (Object[] tuple : result) {
tuple[0]; //name
tuple[1]; // email
}