dao 服务层和控制器层中的 Spring MVC Transactional

亚历山大·图卡诺夫

我正在将 Spring MVC 与 Spring 数据一起使用。

我的问题的简单示例:

我的 dao 服务类:

@Service
@AllArgsConstructor
@Transactional
public class FooService{
    private FooRepository fooRepo;

    public Foo save(Foo foo){
        return fooRepo.save(foo);
    }
}

和控制器:

@Controller
@AllArgsConstructor
@Transactional //if I remove this, method add does not save a foo. 
        //But I don't understand why, because FooService already has @Transactional annotation
public class FooController{
    
    private FooService fooService;

    @PostMapping("/add")
    public String add(@RequestParam("programName") String programName, @RequestParam("id") long id){
        Foo foo = fooService.findById(id).get();
        foo.setProgramName(programName);
        fooService.save(foo);
        return "somePage";
    }
}

如果我从控制器类中删除 @Transaction 注释,方法 save 将不会更新 foo 对象。而且我不明白如果我已经用这个注释标记了服务类,为什么我应该用@Transactional 注释标记控制器?

############ 更新 ####################

简单详细说明:

我有计划和教育实体。一个 Program 有多个 Education,Education 实体有外键 program_id。有一个带有程序表单的页面,有字段:程序 ID,程序主题,...,以及带有以逗号分隔的教育 ID 列表的字段。

我正在尝试更新程序中的教育列表,因此我在页面表单中添加了一个新的教育 ID,然后单击保存。通过调试器我看到,那个新的教育已经出现在程序中,但更改没有出现在数据库中。

@Controller
@RequestMapping("/admin/program")
@AllArgsConstructor //this is lombok, all services autowired by lombok with through constructor parameters
@Transactional//if I remove this, method add does not save a foo. 
        //But I don't understand why, because FooService already has @Transactional annotation
public class AdminProgramController {

    private final ProgramService programService;
    private final EducationService educationService;
    @PostMapping("/add")
    public String add(@RequestParam("themeName") String themeName, @RequestParam("orderIndex") int orderIndex,
                                  @RequestParam(value = "educationList", defaultValue = "") String educationList,
                      @RequestParam(value = "practicalTestId") long practicalTestId){

        saveProgram(themeName, orderIndex, educationList, practicalTestId);
        return "adminProgramAdd";
    
    private Program saveProgram(long programId, String themeName, int orderIndex, String educationList, long practicalTestId){
        
        List<Long> longEducationList = Util.longParseEducationList(parsedEducationList); //this is list of Education id separeted by commas that I load from page form
        //creating new program and set data from page form
        Program program = new Program();
        program.setId(programId);
        program.setThemeName(themeName);
        program.setOrderIndex(orderIndex);

        //starting loop by education id list
        longEducationList.stream()
                .map(educationRepo::findById)
                .filter(Optional::isPresent)
                .map(Optional::get)
                .forEach(edu->{
                    //linking program and education
                    program.getEducationList().add(edu);
                    edu.setProgram(program);
                });

        //saving new program or updating by service if there is one already
        Program savedProgram = programService.save(program);
        //saving education with updated program
        for(Education edu : savedProgram.getEducationList())
        {
            educationService.save(edu);
        }

        return savedProgram;
    }
}

程序服务:

@Service
@AllArgsConstructor //this is lombok, all services autowired by lombok with throught constructor parameters
@Transactional
public class ProgramService {
    private ProgramRepo programRepo;

    //other code here.....

    public Program save(Program program) {
        Optional<Program> programOpt = programRepo.findById(program.getId());

        //checking if the program is already exist, then update it paramateres
        if(programOpt.isPresent()){
            Program prgm = programOpt.get();
            prgm.setThemeName(program.getThemeName());
            prgm.setOrderIndex(program.getOrderIndex());
            prgm.setPracticalTest(program.getPracticalTest());
            prgm.setEducationList(program.getEducationList());
            return programRepo.save(prgm);
        }
        //if not exist then just save new program
        else{
            return programRepo.save(program);
        }
    }
}

教育服务

@Service
@AllArgsConstructor //this is lombok, all services autowired by lombok with throught constructor parameters
@Transactional
public class EducationService {
    private EducationRepo educationRepo;

    //other code here....

    public Education save(Education education){
        return educationRepo.save(education);
    }

}

计划实体:

@Entity
@ToString(exclude = {"myUserList", "educationList", "practicalTest"})
@Getter
@Setter
@NoArgsConstructor
public class Program implements Comparable<Program>{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    @Column(name = "theme_name")
    private String themeName;
    @Column(name = "order_index")
    private int orderIndex; //from 1 to infinity

    @OneToMany(mappedBy = "program", fetch = FetchType.LAZY)
    @OrderBy("orderIndex asc")
    private List<Education> educationList = new ArrayList<>();


    @OneToMany(mappedBy = "program", fetch = FetchType.LAZY)
    private List<MyUser> myUserList = new ArrayList<>();

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "test_id")
    private PracticalTest practicalTest;



    public Program(int orderIndex, String themeName) {
        this.orderIndex = orderIndex;
        this.themeName = themeName;
    }

    public Program(long id) {
        this.id = id;
    }

    //other code here....

}

教育实体:

@Entity
@ToString(exclude = {"program", "myUserList"})
@Getter
@Setter
@NoArgsConstructor
public class Education implements Comparable<Education>{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String link;
    @Column(name = "order_index")
    private int orderIndex;
    private String type;
    private String task;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "program_id")
    private Program program;

    @OneToMany(mappedBy = "education", fetch = FetchType.LAZY)
    private List<MyUser> myUserList = new ArrayList<>();

    public Education(String link, int orderIndex, String task, Program program) {
        this.link = link;
        this.orderIndex = orderIndex;
        this.task = task;
        this.program = program;

    }

    //other code here....
}

程序回购:

@Repository
public interface ProgramRepo extends CrudRepository<Program, Long> {
    Optional<Program> findByPracticalTest(PracticalTest practicalTest);
    Optional<Program> findByOrderIndex(int orderIndex);
    List<Program> findByIdBetween(long start, long end);


}

教育回购:

@Repository
public interface EducationRepo extends CrudRepository<Education, Long> {
    Optional<Education> findByProgramAndOrderIndex(Program program, int orderIndex);

    @Query("select MAX(e.orderIndex) from Education e where e.program.id = ?1")
    int findLastEducationIndexByProgramId(long programId);
}
亚历山大·图卡诺夫

我认为问题是在一个事务中创建并保存在另一个事务中的程序对象。这就是为什么如果我将 Transactional 放在控制器上它可以工作。有两种方法可以解决问题:

  1. 在控制器上没有事务性:那么我必须首先保存教育对象,因为它有程序 id 字段,然后保存程序对象。
  2. 在控制器上使用事务性:那么保存顺序无关紧要,因为保存对象发生在一个事务中

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

如何在Spring MVC中实现控制器和服务层之间的正确交互

在控制器或服务层中的Spring MVC中进行验证?

使用Junit测试Spring MVC和Hibernate的DAO层和Service层的步骤

在Spring Java中减少服务层和DAO

Spring MVC与Web应用程序中的服务,控制器和数据访问层有何关系?

Spring MVC中的@Transactional显示问题

将 @Transactional 方法结果从 Service 传递到控制器层 Spring Boot

为什么我们不应该使用Spring MVC控制器@Transactional?

如何写JUnit测试用例休息控制器,服务和使用springboot DAO层?

Spring Roo vs Appfuse生成服务/ dao层

Java ee EJB中的服务层和dao层

使用Resteasy / Hibernate / Spring简化DAO层

了解SPRING DATA JPA(DAO层)项目

Spring Boot JPA @Transactional @Service不会更新,但是控制器中的@Transactional会更新

Dao层中的Spring Session用户信息检索

3层架构样式(包括MVC)中的DAL,DTO和DAO有什么区别

Spring框架中的DAO和Service层到底是什么?

在DAO层之外使用@ org.springframework.transaction.annotation.Transactional?

JSF控制器,服务和DAO

`@ Transactional`对于Spring 2控制器不起作用

Spring @Transactional方法和类

Spring MVC最佳实践:为什么将事务放在服务而不是DAO上?

服务和DAO层的职责和使用

MVC服务层-每个控制器或其他设计的服务?

服务和DAO层的JUNIT测试

仅创建服务层和DAO层(接口+实现)或实现

在服务层中处理Dao异常

当Servlet,JSP和轻量级DAO层可以工作时,为什么要使用MVC框架?

Spring MVC中具有URL版本控制的控制器继承和模糊映射