Spring Boot + JPA在处理带有多选项(select multiple)的表单时出现问题。

huangapple go评论49阅读模式
英文:

Spring boot + JPA problem with post mapping form where is select multiple

问题

我有一个PostMapping表单,用户可以创建会议并邀请员工。我的问题是员工没有保存到数据库中。以下是我的MeetingDTO:

@Data
@Builder
public class MeetingDto {
    private Long id;
    @NotEmpty(message = "Content could not be empty")
    private String contentOfMeeting;
    @FutureOrPresent
    private LocalDateTime startOfMeeting;
    private LocalDateTime endOfMeeting;
    private Status status;
    private List<Employee> employees;
    private Visitor visitor;
}

以下是我的控制器:

@GetMapping("/visitors/new-meeting")
public String createMeetingForm(Model model) {
    List<Employee> employeeList = employeeRepository.findAll();
    model.addAttribute("employeeList", employeeList);
    model.addAttribute("meeting", new Meeting());
    return "visitors-createAMeeting";
}

@PostMapping("/visitors/new-meeting")
public String saveMeeting(@ModelAttribute("meeting") MeetingDto meetingDto) {
    String nameOfVisitor;

    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    if (principal instanceof UserDetails) {
        nameOfVisitor = ((UserDetails)principal).getUsername();
    } else {
        nameOfVisitor = principal.toString();
    }

    Long visitorId = visitorRepository.findByEmailAddress(nameOfVisitor).getId();
    meetingDto.setVisitor(visitorRepository.findById(visitorId).orElse(null));
    meetingService.createMeeting(visitorId, meetingDto);
    return "redirect:/visitors/home";
}

ServiceImpl:

@Service
public class MeetingServiceImpl implements MeetingService {
    private MeetingRepository meetingRepository;
    private EmployeeRepository employeeRepository;
    private VisitorRepository visitorRepository;

    @Autowired
    public MeetingServiceImpl(MeetingRepository meetingRepository, EmployeeRepository employeeRepository,
                              VisitorRepository visitorRepository) {
        this.meetingRepository = meetingRepository;
        this.employeeRepository = employeeRepository;
        this.visitorRepository = visitorRepository;
    }

    private Meeting mapToMeeting(MeetingDto meetingDto) {
        return Meeting.builder()
                .id(meetingDto.getId())
                .contentOfMeeting(meetingDto.getContentOfMeeting())
                .startOfMeeting(meetingDto.getStartOfMeeting())
                .endOfMeeting(meetingDto.getEndOfMeeting())
                .status(Status.valueOf(String.valueOf(Status.REJECTED)))
                .employees(meetingDto.getEmployees())
                .build();
    }

    @Override
    public void createMeeting(Long visitorId, MeetingDto meetingDto) {
        Visitor visitor = visitorRepository.findById(visitorId).orElse(null);
        Meeting meeting = mapToMeeting(meetingDto);
        meeting.setVisitor(visitor);
        meeting.setEmployees(meetingDto.getEmployees());
        meetingRepository.save(meeting);
    }
}

以及我的GetMapping模板:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html">
<head>
    <meta charset="UTF-8">
    <title>Create a meeting</title>
    <link rel="stylesheet"
          href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
          integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l"
          crossorigin="anonymous" />
</head>
<body>
<div class="container">
    <h3>Create a meeting</h3>
    <hr/>
    <form action="#" th:action="@{/visitors/new-meeting}" th:object="${meeting}" method="post">
        <p>Content: <input type="text" id="content" name="content" th:field="*{contentOfMeeting}" placeholder="Content"></p>
        <p th:if="${#fields.hasErrors('contentOfMeeting')}" class="text-danger" th:errors="*{contentOfMeeting}"></p>
        <p>Start of meeting: <input type="datetime-local" id="start" name="start" th:field="*{startOfMeeting}" placeholder="Start of meeting"></p>
        <p th:if="${#fields.hasErrors('startOfMeeting')}" class="text-danger" th:errors="*{startOfMeeting}"></p>
        <p>End of meeting: <input type="datetime-local" id="end" name="email" th:field="*{endOfMeeting}" placeholder="End of meeting"></p>
        <p></pd><span th:if="${#fields.hasErrors('endOfMeeting')}" th:errors="*{endOfMeeting}" class="text-danger">End of meeting can not be before start of meeting</span></p>
        <label>Employees: </label>
        <p>To select more than one employee please click ctrl</p>
        <select id="employee" class="form-control" th:field="${employeeList}" multiple name="employeeList">
            <option th:each="employee : ${employeeList}" th:value="${employee.id}" th:text="${employee.name + ' ' + employee.surname}"></option>
        </select>
        <br>
        <p><input type="submit" value="Submit"/></p>
        <br>
        <h6><a th:href="@{/logout}">Logout</a></h6>
        <br>
    </form>
</div>
</body>
</html>

请您检查一下,看看能否帮助解决这个问题。

英文:

I have a PostMapping whith form where user can create a meeting and invite employees. My problem is that the employees are not saved to the database.
Here is my MeetingDTO:

@Data
@Builder
public class MeetingDto {
private Long id;
@NotEmpty(message = &quot;Content could not be empty&quot;)
private String contentOfMeeting;
@FutureOrPresent
private LocalDateTime startOfMeeting;
private LocalDateTime endOfMeeting;
private Status status;
private List&lt;Employee&gt; employees;
private Visitor visitor;
}

Here is my controller:

@GetMapping(&quot;/visitors/new-meeting&quot;)
public String createMeetingForm(Model model) {
List&lt;Employee&gt; employeeList = employeeRepository.findAll();
model.addAttribute(&quot;employeeList&quot;, employeeList);
model.addAttribute(&quot;meeting&quot;, new Meeting());
return &quot;visitors-createAMeeting&quot;;
}
@PostMapping(&quot;/visitors/new-meeting&quot;)
public String saveMeeting(@ModelAttribute(&quot;meeting&quot;) MeetingDto meetingDto) {
String nameOfVisitor;
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
nameOfVisitor = ((UserDetails)principal).getUsername();
} else {
nameOfVisitor = principal.toString();
}
Long visitorId = visitorRepository.findByEmailAddress(nameOfVisitor).getId();
meetingDto.setVisitor(visitorRepository.findById(visitorId).orElse(null));
meetingService.createMeeting(visitorId, meetingDto);
return &quot;redirect:/visitors/home&quot;;
}

ServiceImpl:

@Service
public class MeetingServiceImpl implements MeetingService {
private MeetingRepository meetingRepository;
private EmployeeRepository employeeRepository;
private VisitorRepository visitorRepository;
@Autowired
public MeetingServiceImpl(MeetingRepository meetingRepository, EmployeeRepository employeeRepository,
VisitorRepository visitorRepository) {
this.meetingRepository = meetingRepository;
this.employeeRepository = employeeRepository;
this.visitorRepository = visitorRepository;
}
private Meeting mapToMeeting(MeetingDto meetingDto) {
return Meeting.builder()
.id(meetingDto.getId())
.contentOfMeeting(meetingDto.getContentOfMeeting())
.startOfMeeting(meetingDto.getStartOfMeeting())
.endOfMeeting(meetingDto.getEndOfMeeting())
.status(Status.valueOf(String.valueOf(Status.REJECTED)))
.employees(meetingDto.getEmployees())
.build();
}
@Override
public void createMeeting(Long visitorId, MeetingDto meetingDto) {
Visitor visitor = visitorRepository.findById(visitorId).orElse(null);
Meeting meeting = mapToMeeting(meetingDto);
meeting.setVisitor(visitor);
meeting.setEmployees(meetingDto.getEmployees());
meetingRepository.save(meeting);
}
}

And my template for GetMapping:

&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot; xmlns:th=&quot;http://www.thymeleaf.org&quot; xmlns=&quot;http://www.w3.org/1999/html&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;title&gt;Create a meeting&lt;/title&gt;
&lt;link rel=&quot;stylesheet&quot;
href=&quot;https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css&quot;
integrity=&quot;sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l&quot;
crossorigin=&quot;anonymous&quot; /&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div class=&quot;container&quot;&gt;
&lt;h3&gt;Create a meeting&lt;/h3&gt;
&lt;hr/&gt;
&lt;form action=&quot;#&quot; th:action=&quot;@{/visitors/new-meeting}&quot; th:object=&quot;${meeting}&quot; method=&quot;post&quot;&gt;
&lt;p&gt;Content: &lt;input type=&quot;text&quot; id=&quot;content&quot; name=&quot;content&quot; th:field=&quot;*{contentOfMeeting}&quot; placeholder=&quot;Content&quot;&gt;&lt;/p&gt;
&lt;p th:if=&quot;${#fields.hasErrors(&#39;contentOfMeeting&#39;)}&quot; class=&quot;text-danger&quot; th:errors=&quot;*{contentOfMeeting}&quot;&gt;&lt;/p&gt;
&lt;p&gt;Start of meeting: &lt;input type=&quot;datetime-local&quot; id=&quot;start&quot; name=&quot;start&quot; th:field=&quot;*{startOfMeeting}&quot; placeholder=&quot;Start of meeting&quot;&gt;&lt;/p&gt;
&lt;p th:if=&quot;${#fields.hasErrors(&#39;startOfMeeting&#39;)}&quot; class=&quot;text-danger&quot; th:errors=&quot;*{startOfMeeting}&quot;&gt;&lt;/p&gt;
&lt;p&gt;End of meeting: &lt;input type=&quot;datetime-local&quot; id=&quot;end&quot; name=&quot;email&quot; th:field=&quot;*{endOfMeeting}&quot; placeholder=&quot;End of meeting&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;/pd&gt;&lt;span th:if=&quot;${#fields.hasErrors(&#39;endOfMeeting&#39;)}&quot; th:errors=&quot;*{endOfMeeting}&quot; class=&quot;text-danger&quot;&gt;End of meeting can not be before start of meeting&lt;/span&gt;&lt;/p&gt;
&lt;label&gt;Employees: &lt;/label&gt;
&lt;p&gt;To select more than one employee please click ctrl&lt;/p&gt;
&lt;select id=&quot;employee&quot; class=&quot;form-control&quot; th:field=&quot;${employeeList}&quot; multiple name=&quot;employeeList&quot;&gt;
&lt;option th:each=&quot;employee : ${employeeList}&quot; th:value=&quot;${employee.id}&quot; th:text=&quot;${employee.name + &#39; &#39; + employee.surname}&quot;&gt;&lt;/option&gt;
&lt;/select&gt;
&lt;br&gt;
&lt;p&gt;&lt;input type=&quot;submit&quot; value=&quot;Submit&quot;/&gt;&lt;/p&gt;
&lt;br&gt;
&lt;h6&gt;&lt;a th:href=&quot;@{/logout}&quot;&gt;Logout&lt;/a&gt;&lt;/h6&gt;
&lt;br&gt;
&lt;/form&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;

Could you be so kind to take a look and help me to solve the issue?

I tried refactor template and controller but the problem still exist.

答案1

得分: 0

在审查您的代码时,我注意到在JPA/Hibernate中处理关系时可能存在问题。当涉及相关实体时,比如Meeting和Employee,在这种情况下,正确管理关系的两端非常重要。

在您的代码中,您使用meeting.setEmployees(meetingDto.getEmployees());将员工分配给会议。这是正确的,但根据您的关系设置,这可能不足够。您可能还需要为每个员工设置会议。例如,您可以遍历每个员工并添加会议:

List<Employee> employees = meetingDto.getEmployees();
for(Employee employee : employees) {
    employee.getMeetings().add(meeting); // 假设Employee类中有一个getMeetings()方法
}

此代码段将当前会议添加到每个员工的会议列表中。当保存会议时,相关员工也应该被更新。

当然,这个建议是基于在使用JPA/Hibernate时的常见实践,并且具体的实现可能需要根据您的实际实体配置进行调整。重要的是确保Meeting和Employee实体之间的关系被正确设置,并具有适当的级联设置。您可能需要设置CascadeType.PERSISTCascadeType.MERGE来确保在保存会议时保存员工的更改。

如果问题仍然存在,最好仔细查看定义它们之间关系的Employee和Meeting实体的部分。这将允许更精确地解决您的问题。

修订后的答案

挑战似乎在于正确分配只选定的员工给会议,而不是像目前发生的那样分配所有员工。

从您的表单的外观来看,提交表单时可能只发送了所选员工的ID到服务器。因此,我们应该调整您的MeetingDto以包含这些ID的列表。下面是如何做的:

public class MeetingDto {
    // 其他字段...
    private List<Long> employeeIds; // 替换自List<Employee> employees
    // 其他字段...
}

接下来,我们可以修改MeetingService中的createMeeting方法来处理这些员工ID:

@Override
public void createMeeting(Long visitorId, MeetingDto meetingDto) {
    Visitor visitor = visitorRepository.findById(visitorId).orElse(null);
    Meeting meeting = mapToMeeting(meetingDto);
    meeting.setVisitor(visitor);

    List<Employee> selectedEmployees = employeeRepository.findAllById(meetingDto.getEmployeeIds()); // 通过ID检索员工
    meeting.setEmployees(selectedEmployees);

    meetingRepository.save(meeting);
}

最后,我们需要确保您的表单发送了所选员工的ID。您表单中的select元素应该修改为如下所示:

<select id="employee" class="form-control" th:field="*{employees}" multiple name="employeeList">
    <option th:each="employee : ${employeeList}" th:value="${employee.id}" th:text="${employee.name + ' ' + employee.surname}"></option>
</select>

通过这些修改,您的表单将传输所选员工的ID到服务器。然后,您的服务可以根据这些ID从数据库中检索相关的员工。结果,只有所选员工将与会议相关联。

英文:

While reviewing your code, I noticed a potential issue with how the relationships are handled in JPA/Hibernate. When you're dealing with related entities, in this case Meeting and Employee, it's crucial to manage both sides of the relationship correctly.

In your code, you're assigning employees to a meeting using meeting.setEmployees(meetingDto.getEmployees());. This is correct, but depending on your relationship setup, it may not be sufficient. You might also need to set the meeting to each employee. For example, you could iterate over each employee and add the meeting:

List&lt;Employee&gt; employees = meetingDto.getEmployees();
for(Employee employee : employees) {
    employee.getMeetings().add(meeting); // Assumes a getMeetings() method in Employee class
}

This snippet adds the current meeting to the list of meetings for each employee. When you save your meeting, the related employees should also be updated.

Of course, this suggestion is based on common practice when using JPA/Hibernate, and the specific implementation may need adjustment according to your actual entity configuration. It's important to ensure the relationship between Meeting and Employee entities is set correctly, with appropriate cascading settings. You might need to set CascadeType.PERSIST or CascadeType.MERGE to make sure the changes to the employees are stored when saving the meeting.

If the problem persists, it would be helpful to take a closer look at the parts of your Employee and Meeting entities that define their relationship. This would allow for a more precise solution to your problem.

Revised Answer

The challenge seems in correctly assigning only the selected employees to the meeting, rather than all the employees as currently happens.

From the look of your form, it seems likely that only the IDs of the selected employees are being sent to the server when the form is submitted. So, we should adjust your MeetingDto to hold a list of these IDs. Here's how:

public class MeetingDto {
    // Other fields...
    private List&lt;Long&gt; employeeIds; // Replaced from List&lt;Employee&gt; employees
    // Remaining fields...
}

Next, we can modify the createMeeting method within your MeetingService to handle these employee IDs:

@Override
public void createMeeting(Long visitorId, MeetingDto meetingDto) {
    Visitor visitor = visitorRepository.findById(visitorId).orElse(null);
    Meeting meeting = mapToMeeting(meetingDto);
    meeting.setVisitor(visitor);

    List&lt;Employee&gt; selectedEmployees = employeeRepository.findAllById(meetingDto.getEmployeeIds()); // Retrieve employees by their IDs
    meeting.setEmployees(selectedEmployees); 

    meetingRepository.save(meeting);
}

Lastly, we need to ensure your form is sending the IDs of the selected employees. Your select element in the form should be modified to look like this:

&lt;select id=&quot;employee&quot; class=&quot;form-control&quot; th:field=&quot;*{employees}&quot; multiple name=&quot;employeeList&quot;&gt;
    &lt;option th:each=&quot;employee : ${employeeList}&quot; th:value=&quot;${employee.id}&quot; th:text=&quot;${employee.name + &#39; &#39; + employee.surname}&quot;&gt;&lt;/option&gt;
&lt;/select&gt;

With these alterations, your form will be transmitting the IDs of the selected employees to the server. Then, your service can retrieve the relevant employees based on these IDs from the database. As a result, only the selected employees will be associated with the meeting.

答案2

得分: 0

最终,我通过对ServiceImpl中的更改解决了这个问题:

@Override
public void createMeeting(Long visitorId, MeetingDto meetingDto) {
    Visitor visitor = visitorRepository.findById(visitorId).orElse(null);
    Meeting meeting = mapToMeeting(meetingDto);
    meeting.setVisitor(visitor);
    List<Employee> selectedEmployees = employeeRepository.findAllById(meetingDto.getEmployeeIds());
    meeting.setEmployees(selectedEmployees);
    for (Employee employee : selectedEmployees) {
        employee.getMeeting().add(meeting);
    }
}

感谢您的支持,@NaveenNegi。

英文:

Finaly I solved the issue with this changes in ServiceImpl:

@Override
public void createMeeting(Long visitorId, MeetingDto meetingDto) {
Visitor visitor = visitorRepository.findById(visitorId).orElse(null);
Meeting meeting = mapToMeeting(meetingDto);
meeting.setVisitor(visitor);
List&lt;Employee&gt; selectedEmployees = employeeRepository.findAllById(meetingDto.getEmployeeIds());
meeting.setEmployees(selectedEmployees);
for (Employee employee : selectedEmployees) {
employee.getMeeting().add(meeting);
}
}

Thank you for your support @NaveenNegi.

huangapple
  • 本文由 发表于 2023年6月2日 01:36:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/76384389.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定