【问题标题】:Spring: Adding one record to a List suddenly creates a duplicateSpring:将一条记录添加到列表中会突然创建重复项
【发布时间】:2018-11-20 17:12:43
【问题描述】:

为了学习 Spring,我正在玩一些非常简单的页面来理解 Spring 的一般注释和行为。准确地说,我创建了一个页面来显示Task 的列表和另一个页面来添加一个新的Task。我使用@SessionAttibutes 将任务列表保存在会话中,而不是在每次访问任务列表页面时调用服务来检索Task 的完整列表。

这是我的TaskService

@Service
public class TaskService {
  private List<Task> taskCache = new ArrayList<Task>();

  public List<Task> retrieveTasks() {
    System.out.println("GETTING TASK LIST FOR THE FIRST TIME");
    return taskCache;
  }

  public Task addTask(String title, String desc, Date targetDate) {
    Task newTask = new Task(title, desc, targetDate);
    taskCache.add(newTask);

    return newTask;
  }
}

这是我的TaskController

@Controller
@SessionAttributes("taskList")
public class TaskController {
  @Autowired
  private TaskService taskService;

  @GetMapping("/task-view")
  public String showTaskList() {
    return "task/view";
  }

  @GetMapping("/task-add")
  public String showAddTaskForm(@ModelAttribute("task") Task newTask) {
    return "task/add";
  }

  @PostMapping("/task-add")
  public String addTask(ModelMap model, @ModelAttribute("task") Task newTask, @ModelAttribute("taskList") List<Task> taskList) {
    System.out.println("ADDING NEW TASK: " + taskList.size());
    taskList.add(taskService.addTask(newTask.getTitle(), newTask.getDesc(), new Date()));
    System.out.println("DONE ADDING NEW TASK: " + taskList.size());
    return "redirect:/task-view";
  }

  @ModelAttribute("taskList")
  public List<Task> taskList() {
    System.out.println("CALLING TASK SERVICE TO GET LIST OF TASKS");
    return taskService.retrieveTasks();
  }
}

这是我的view.jsp

<html>
  <head>
    <title>Task Page</title>
  </head>
  <body>
    This is your task list:
    ${taskList}

    <div>
      <a href="task-add">Add a task</a>
    </div>
  </body>
</html>

这是我的add.jsp

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<html>
  <head>
    <title>Task Page</title>
  </head>
  <body>
    <form:form method="POST" action="/task-add" modelAttribute="task">
      <div>
        <form:label path="title">Title</form:label>
        <form:input path="title" />
      </div>

      <div>
        <form:label path="desc">Description</form:label>
        <form:input path="desc" />
      </div>

      <div>
        <input type="submit" value="Add">
      </div>
    </form:form>
  </body>
</html>

就会话范围的属性而言,它按我的预期工作。 TaskService.retrieveTasks() 函数仅在我第一次访问 view.jsp 时被调用。随后,从 Session 中提取相同的任务列表。

问题是当我尝试在add.jsp 页面上添加两个新的Task 时,我在控制台中看到了以下几行。

CALLING TASK SERVICE TO GET LIST OF TASKS
GETTING TASK LIST FOR THE FIRST TIME
ADDING NEW TASK: 0
DONE ADDING NEW TASK: 2
ADDING NEW TASK: 2
DONE ADDING NEW TASK: 4

当我第一次访问view.jsp 时,打印了第一两行,这是正确的。但是,我不知道ADDING NEW TASKDONE ADDING NEW TASK 行之间发生了什么。如果有人能向我解释为什么下面的行在我的 Task 列表中添加重复项,我将不胜感激

taskList.add(taskService.addTask(newTask.getTitle(), newTask.getDesc(), new Date()));

我还尝试删除taskList.add() 部分,只对taskService.addTask() 进行了服务调用。在这种情况下,没有重复,我的 taskList 更新为一条新记录。但是,当TaskService 类中没有注入时,我不明白taskService 如何间接修改我的taskList

我错过了什么吗?

【问题讨论】:

  • 您引用同一个列表两次,因此将Task 两次添加到同一个列表中。缓存的危险...与其尝试同步缓存,不如在服务中添加一次,然后刷新缓存列表。而不是尝试手动使它们保持最新...
  • @M.Deinum 感谢您指出我的错误。我试图实现一个简单的例子来学习 Spring。我以为我在某种程度上误解了 Spring,但我从来没有意识到我犯了这样一个基本的 Java 错误。

标签: java spring spring-mvc spring-boot session


【解决方案1】:
  @ModelAttribute("taskList")
  public List<Task> taskList() {
    System.out.println("CALLING TASK SERVICE TO GET LIST OF TASKS");
    return taskService.retrieveTasks();
  }

我认为这就是你所缺少的。 taskList() 方法返回 taskCache 列表,这是 taskService 修改的列表。所以 taksList 和 taskCache 是同一个对象。

【讨论】:

    【解决方案2】:

    问题出在方法retrieveTasks

    public List<Task> retrieveTasks() {
        System.out.println("GETTING TASK LIST FOR THE FIRST TIME");
        return taskCache;
    }
    

    此方法返回对taskCache 对象的引用。有权访问此引用的每个人都可以修改此可变列表。例如。在 PHP 中,如果您返回一个数组,默认情况下,您将收到该数组的副本。不是在 Java 中。

    你应该像这样修改方法:

    public List<Task> retrieveTasks() {
            System.out.println("GETTING TASK LIST FOR THE FIRST TIME");
            return new ArrayList<>(taskCache); // create copy of taskCache
    }
    

    事实上,这是真正的服务会做的事情。数据将存储在数据库中,但您不会返回对该数据的直接引用。您总是会从数据库中提取当前数据并将其具体化到一个全新的列表中。

    【讨论】:

    • 该死的。谢谢你。我花了太多时间思考 Spring 以至于我没有意识到 Java 中的一个基本错误!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-06-26
    • 1970-01-01
    • 2020-07-07
    • 2021-11-25
    • 1970-01-01
    • 2021-08-01
    • 2021-12-24
    相关资源
    最近更新 更多