首页 > 解决方案 > 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。有一个带有 Program form 的页面,有字段:program id,program theme,...,以及带有以逗号分隔的教育 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);
}

标签: javaspring-mvctransactionsspring-datadao

解决方案


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

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

推荐阅读