I am using a Spring framework with thymeleaf and mysql.
I am getting the error
org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'name' cannot be found on null
This error is being caused by the ${selectProject.client.name} in the html code.
I have written a form to allow a user to add information about a project. As part of the form, there is a dropdown list of the different project names. However, in the dropdown list I also want it to give the name of the client alongside the name of the project.
My html code:
<form action="#" id="informationForm" method="post" th:action="#{/add-information}" th:object="${information}">
<div class="row">
<div class="form-group col-12">
<label class="col-form-label" for="project">Project</label>
<select class="form-control" id="project" th:field="*{project}">
<option value="0">Please select a project</option>
<option th:each="selectProject : ${projectList}"
th:text="${selectProject.client.name} + ' - ' +${selectProject.name}"
th:value="${selectProject.id}"></option>
</select>
Here is the information controller:
#Controller
#Slf4j
#SessionAttributes({"project", "information"})
public class InformationController {
private final InformationService informationService;
private final ProjectsService projectsService;
#Autowired
public InformationController(final ProjectsService projectsService,
final InformationService informationService) {
this.projectsService = projectsService;
this.informationService = informationService;
}
#ModelAttribute("information")
public Information getInformation() {
return informationService.createEmptyInformation();
}
#ModelAttribute("projectList")
public List<Project> getProjects() {
return projectsService.getProjects();
}
#GetMapping("add-information")
public String showAddInformationForm(Model model, #ModelAttribute("information") Information information) {
information = informationService.createEmptyInformation();
model.addAttribute(information);
return "add-information";
}
#PostMapping("add-information")
public String addInformationForm(#Valid Information information, BindingResult result, Model model) {
if (result.hasErrors()) {
return "add-information";
}
Long id = informationService.createInformationFromInput(information);
return "redirect:/homepage";
}
The relevant methods in the information service are:
public Information createEmptyInformation() {
return new Information();
}
public Long createInformationFromInput(Information information) {
try {
informationDao.save(information);
} catch (Exception ex) {
log.error("Failed to save information for {} because of {}", information.getId(), ex.getLocalizedMessage());
}
return information.getId();
}
and in the projects services:
public List<Project> getProjects() {
return projectDao.findAll();
}
Clients have a many to one relationship with projects
Project domain:
#Getter
#Setter
#Entity
#Table(name = "projects")
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "client_id")
private Client client;
#Column(name = "name")
private String name;
Client domain:
#Getter
#Setter
#Entity
#Table(name = "clients")
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank(message = "Name is required")
#Size(min = 5, max = 80, message = "Name must be between 5 and 80 characters")
#Column(name = "name", nullable = false)
private String name;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "client_id")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private List<Project> projects;
Information domain:
#Getter
#Setter
#Entity
#Table(name = "information")
public class Information {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne (fetch = FetchType.LAZY)
#JoinColumn(name = "project_id")
private Project project;
#Column(name = "date")
private Date date;
Related
I use OneToOne in the spring data JPA and I want to delete a record from the Address table without touching the user. But I can't.
If I remove User, in this case Address is removed, that's good.
But how can you delete an Address without touching the User?
https://github.com/myTestPercon/TestCascade
User.Java
#Entity
#Table(name = "user", schema = "testCascade")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
private Address address;
// Getter and Setter ...
}
Address.java
#Entity
#Table(name = "address", schema = "testCascade")
public class Address implements Serializable {
#Id
private Long id;
#Column(name = "city")
private String city;
#OneToOne
#MapsId
#JoinColumn(name = "id")
private User user;
// Getter and Setter ...
}
DeleteController.java
#Controller
public class DeleteController {
#Autowired
ServiceJpa serviceJpa;
#GetMapping(value = "/deleteAddressById")
public String deleteAddressById () {
serviceJpa.deleteAddressById(4L);
return "redirect:/home";
}
}
You got your mapping wrong thats all is the problem .
try the below and see
User.java
#Entity
#Table(name = "user", schema = "testCascade")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="foriegn key column in user table for address example.. address_id")
private Address address;
// Getter and Setter ...
}
Address.java
#Entity
#Table(name = "address", schema = "testCascade")
public class Address implements Serializable {
#Id
private Long id;
#Column(name = "city")
private String city;
//name of the address variable in your user class
#OneToOne(mappedBy="address",
cascade={CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.REFRESH})
private User user;
// Getter and Setter ...
}
In order to solve this problem, you need to read the hibernate Documentation Hibernate Example 162, Example 163, Example 164.
And also I recommend to look at this is Using #PrimaryKeyJoinColumn annotation in spring data jpa
This helped me in solving this problem.
And also you need to specify the parameter orphanRemoval = true
User.java
#Entity(name = "User")
#Table(name = "user", schema = "testother")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private Address address;
public void addAddress(Address address) {
address.setUser( this );
this.address = address;
}
public void removeAddress() {
if ( address != null ) {
address.setUser( null );
this.address = null;
}
}
// Getter and Setter
}
Address.java
#Entity(name = "Address")
#Table(name = "address", schema = "testother")
public class Address implements Serializable {
#Id
private Long id;
#Column(name = "city")
private String city;
#OneToOne
#MapsId
#JoinColumn(name = "id")
private User user;
// Getter and Setter
}
DeleteController .java
#Controller
public class DeleteController {
#Autowired
ServiceJpa serviceJpa;
#GetMapping(value = "/deleteUser")
public String deleteUser () {
User user = serviceJpa.findUserById(2L).get();
user.removeAddress();
serviceJpa.saveUser(user);
return "/deleteUser";
}
}
Or make a custom SQL query.
#Repository
public interface DeleteAddress extends JpaRepository<Address, Long> {
#Modifying
#Query("delete from Address b where b.id=:id")
void deleteBooks(#Param("id") Long id);
}
public class Address {
#Id
private Long id;
#MapsId
#JoinColumn(name = "id")
private User user;
}
Rename #JoinColumn(name = "id") to #JoinColumn(name = "user_id")
You can't say that the column that will point to user will be the id of the Address
*#Entity
#Table(name = "model_data")
public class ModelData {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "diagram_id")
private DiagramEntity diagram;
//Getter Setter
}
#Entity
#Table(name = "diagram")
public class DiagramEntity {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonProperty(value = "class")
private String aciklama;
#OneToMany(mappedBy = "diagram")
private Set<ModelData> modelData;
//Getter Setter
}*
DiagramEntity has manytoone relation with modeldata entity. Then I post
*{
"modelData": [
{
"diagram": null,
}
],
}*
via postman, DiagramEntity id is automatically updated but I cannot assign id to modelData entity and cannot save to database. what should I do?
I find solution for my problem. I streamed every object and mapped to my entities in the my controller class.
public DiagramEntityDto saveOrUpdate(#RequestBody DiagramEntityDto dto) {
try {
dto.getModelData().stream().map(a ->
this.modelDataService.saveOrUpdate(a)).collect(Collectors.toSet());
return this.diagramService.saveOrUpdate(dto);
} catch (Exception e) {
System.out.println(e.getMessage());
}
return null;
}
Consider the tables where posts and tags exhibit a many-to-many relationship between each other.
The many-to-many relationship is implemented using a third table called post_tags which contains the details of posts and their associated tags.
Post Model
#Entity
#Table(name = "posts")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size(max = 100)
#Column(unique = true)
private String title;
#NotNull
#Size(max = 250)
private String description;
#NotNull
#Lob
private String content;
#NotNull
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "posted_at")
private Date postedAt = new Date();
#NotNull
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "last_updated_at")
private Date lastUpdatedAt = new Date();
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "post_tags",
joinColumns = { #JoinColumn(name = "post_id") },
inverseJoinColumns = { #JoinColumn(name = "tag_id") })
private Set<Tag> tags = new HashSet<>();
public Post() {
}
public Post(String title, String description, String content) {
this.title = title;
this.description = description;
this.content = content;
}
// Getters and Setters (Omitted for brevity)
}
TAG Model
#Entity
#Table(name = "tags")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size(max = 100)
#NaturalId
private String name;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "tags")
private Set<Post> posts = new HashSet<>();
public Tag() {
}
public Tag(String name) {
this.name = name;
}
// Getters and Setters (Omitted for brevity)
}
Problem is
I tried to use an existing tags. and insert happened only on posts & posts_tags table.
Initially i'm Get tag(s) with tagName(s). Once you have the Tag object, you can set it in the Post object and save it.
Like this
Post post = new Post("Hibernate Many to Many Example with Spring Boot",
"Learn how to map a many to many relationship using hibernate",
"Entire Post content with Sample code");
// Create two tags
Tag tag1 = tagService.getTag("Spring Boot");
// Add tag references in the post
post.getTags().add(tag1);
postRepository.save(post);
If I do like that, entry is not available in post_tags table.
Tag Repository and Tag Service:
#Repository
public interface TagRepository extends JpaRepository<Tag, Long> {
#Query("select p from Tag p where p.name = :name")
Tag findByName(#Param("name") String name);
}
#Override
public Tag findByName(String name) {
return repository.findByName(name);
}
I can't let this exception go:
Failed to convert property value of type java.lang.String to required type com.company.springdemo.entity.Product for property productId; nested exception is java.lang.IllegalStateException: Cannot convert value of type java.lang.String to required type com.company.springdemo.entity.Product for property productId: no matching editors or conversion strategy found
Order Model
#Entity
#Table(name = "orders") // naming the table only order, will throw exception
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "order_id")
private Integer orderId;
#OneToOne(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH})
#JoinColumn(name = "product_id")
private Product productId;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH})
#JoinColumn(name = "client_id")
private Client client;
....
Product Model
#Entity
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "product_id")
private Integer id;
#Column(name = "product_name")
private String productName;
#Column(name = "product_serial")
private String productSerial;
...
Client Model
#Entity
#Table(name = "clients")
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#NotEmpty
#Column(name = "first_name")
private String firstName;
#NotEmpty
#Column(name = "last_name")
private String lastName;
#NotEmpty
#Email
#Column(name = "email")
private String email;
#NotEmpty
#Column(name = "location")
private String location;
#OneToMany(mappedBy = "client",cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Order> orders;
Controller, where I save the order with related client and product
#PostMapping("add")
public ModelAndView addOrder( #Validated #ModelAttribute("ords") Order order, BindingResult bindingResult ){
if (bindingResult.hasErrors()) {
System.out.println("Having errors: " + bindingResult.getAllErrors());
Iterable<Product> products = productService.listProducts();
Iterable<Client> clients = clientService.listClients();
System.out.println("Error "+ bindingResult.getAllErrors());
ModelAndView mv = new ModelAndView("orders/add-order");
mv.addObject("products",products);
mv.addObject("clients",clients);
return mv;
}
try {
orderService.saveOrder(order);
} catch (Exception e) {
e.printStackTrace();
}
ModelAndView mv = new ModelAndView("redirect:list");
return mv;
}
Finally, my JSP form View page
<form:form action="add" method="post" modelAttribute="ords">
<label for="productId" >Product Id</label>
<form:select path="productId" >
<c:forEach var="product" items="${products}">
<form:option value="${product.id}">${product.productName}</form:option>
</c:forEach>
</form:select>
<form:errors path="productId"/>
<br>
<label for="client" >Client Id</label>
<form:select path="client" >
<c:forEach var="client" items="${clients}">
<form:option value="${client.id}">${client.id} - ${client.lastName}</form:option>
</c:forEach>
</form:select>
<form:errors path="client"/>
<br>
<input type="submit" value="Place Order">
</form:form>
What am I doing wrong?
You most likely need to build a converter class such as this one :
#Component("facilityConverter")
public class FacilityConverter implements Converter<String, Facility>
{
#Autowired
FacilityService facilityService;
#Override
public Facility convert(String id)
{
return facilityService.findById(Integer.parseInt(id));
}
}
Then, you need to register it by implementing the addFormatters method inside of a configuration class implementing WebMvcConfigurer like so :
#Override
public void addFormatters (FormatterRegistry registry)
{
registry.addConverter((FacilityConverter)ctx.getBean("facilityConverter"));
}
Your entities will then correctly be mapped from a dropdown selection. Also, this might not be part of your issue but you can just build your dropdowns like this :
<form:select name="linkedInterface" path="linkedInterface" id="linkedInterface">
<form:options items="${interfaces}" itemLabel="name" itemValue="id"/>
</form:select>
The productId field is actually a Product object, not an ID (String/int). You need your JSP to use path="productId.id" rather than path="productId".
(Although I'd also suggest you also rename the field product rather than productId.)
<form:select path="product.id">
I think you'll hit the same issue on your <form:select path="client"> too.
I got an issue when trying to load an object attribute in a jsp file.
The model contains a list of objects of type "Evaluation", for each element in the list, all the attributes are correctly loaded except the ones that it has to fetch from another table.
The .jsp file :
<div class="container">
<h1>Liste des Evaluations pour ${etudiant.username}</h1>
<table class="tbl">
<thead>
<th>Module</th>
<th>Note</th>
<th>Remarque</th>
</thead>
<tbody>
<c:forEach items="model.evaluations" var="ev">
<tr>
<td>${ev.examen.module.code}</td> --> Error occurs here
<td>${ev.note}</td>
<td>${ev.remarque}</td>
</tr>.
</c:forEach>
</tbody>
</table>
The Controller :
#RequestMapping(value="/{username}", method=RequestMethod.GET)
public String etudiantEvaluations(
#PathVariable String username, Model model) {
List<Evaluation> evaluations = evalDAO.findAllByEtudiant(username);
Etudiant etudiant = etDAO.findByUsername(username);
model.addAttribute("evaluations", evaluations);
model.addAttribute("etudiant", etudiant);
return "etudiants/listEvaluations";
}
The Evaluation entity :
#Data
#NoArgsConstructor
#EqualsAndHashCode
#Immutable #Entity(name="TEVALUATION")
public class Evaluation {
private enum evaldeliberation {
REUSSITE,
AJOURNEMENT,
REFUS,
ABANDON
}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
protected Long id;
#NotNull
#Min(0) #Max(100)
#Column(updatable = true)
private Double note;
#Column(name="deliberation")
private evaldeliberation delib;
#Column(name="remarque")
private String remarque;
#Column(name="module_code")
private String moduleCode;
private Long examenId;
private String etudiantUsername;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "FKExamen",
insertable = false,
updatable = false)
protected Examen examen;
#ManyToOne
#JoinColumn(name = "FKEtudiant",
insertable = false,
updatable = false)
protected Etudiant etudiant;
#OneToMany(mappedBy="evaluation")
protected List<EvalComp> evalComps = new ArrayList<>();
public Evaluation(Double note, Examen examen, Etudiant etudiant) {
super();
this.examen = examen;
this.etudiant = etudiant;
examen.getEvaluations().add(this);
etudiant.getEvaluations().add(this);
}
}
Examen:
#Data
#EqualsAndHashCode
#NoArgsConstructor
#Entity(name="TEXAMEN")
public class Examen {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(mappedBy = "examen")
private Set<Evaluation> evaluations = new HashSet<>();
#NotNull
#OneToOne(cascade=CascadeType.PERSIST)
#JoinColumn(name="FKmodule")
protected Module module;
public Examen(Module module) {
this.module = module;
}
}
The Query:
#Query("SELECT ev FROM TEVALUATION ev JOIN FETCH ev.examen ex JOIN FETCH ex.module m WHERE ev.etudiant=?1")
List<Evaluation> findAllByEtudiantId(String username);
The getters and setters are generated by Lombok(also tried without it).
Any idea how can I load the attributes ?
Thanks in advance.