4

I have a Java Spring project with JPA persistence using EclipseLink. I want to use JpaRepository interfaces for my entities and the default implementations for most cases but I also need to define a few of my own methods and I need to sometimes override the default methods like save.

My code works when compiled in Eclipse, but I keep getting an ambiguous reference error when compiling with Maven.

What I have done is this (for example to override save because I need to do certain things to the entity to be saved):

public interface ReportRepository extends JpaRepository<Report, Long>, ReportRepositoryCustom {

}
public interface ReportRepositoryCustom {

    public Report save(Report report);
    public int getReportCountForImporter(Long importerId);
    ...

}
public class ReportRepositoryCustomImplementation implements ReportRepositoryCustom {
     public Report save(Report report)  { ... }
     public int getReportCountForImporter(Long importerId) { ... }
}

public class ReportService {
    @Autowired
    private ReportRepository reportRepository;
}

This works fine in Eclipse when I compile it to run on Tomcat. The object ReportRepository reportRepository has methods from JPA repository implementation and my custom methods and the custom save method is called when I call reportRepository.save(...). However, when I do Maven Install, the compiler complains about an ambiguous reference:

[ERROR] /C:/Users/Jarno/git/Korjaamotestiraportointi/src/main/java/fi/testcenter/service/ReportService.java:[40,40] reference to save is ambiguous both method save(fi.testcenter.domain.report.Report) in fi.testcenter.repository.ReportRepositoryCustom and method save(S) in org.springframework.data.repository.CrudRepository match

I've found coding my repositories a bit complicated. I would like to use the ready-made implementations for JPA repositories and not have to code anything extra. My code keeps everything nice and clean. The repository interface to be used as reference in services is named the same way for every entity and methods are also named the same and any custom methods or overrides are done through custom interfaces and implementations. I don't need to write any unnecessary code anywhere. But then I ran into my problem with Maven.

I have managed to compile my code with Maven by first running it in Eclipse Tomcat server. But if I do Maven Clean and then Maven Install, I get a bunch of errors. And obviously I'd like to not have to resort to any kind of a hack in compiling with Maven.

So is there a fix that would allow to do this with Maven? Or is there another way of coding what I want to do here?

hubbabubba
  • 937
  • 1
  • 9
  • 17
  • [Spring JPA CrudRepsoitory class already have save method](https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html#save-S-), why are defning your own with same signature? – sagarr Oct 28 '17 at 17:26
  • 1
    @Sagar Rohankar To override the default method because I need to do certain things before saving the entity to the database. I don't want to name the method differently and have to remember that for certain entities I need to call a custom method with a name other than save. – hubbabubba Oct 28 '17 at 17:32
  • then don't extend `ReportRepositoryCustom` in `ReportRepository` interface. – sagarr Oct 28 '17 at 17:34
  • 1
    The reason it is done so is that when reportRepository is autowired it has methods from default JpaRepository implementation, like delete, and my custom methods. Both are needed. But Mavens compiler gets confused by two methods with same signatures and I dont know if there is any way around this. – hubbabubba Oct 28 '17 at 17:47
  • may be this help then: https://stackoverflow.com/questions/13036159/spring-data-override-save-method – sagarr Oct 28 '17 at 17:51
  • I believe in that question the answer by Mauro Molinari is precisely what I'm trying to do, but in comments to that answer someone is saying he's getting an ambiguous reference error just like I am. The suggestion by Balamaci Serban is way too complicated. So I think I'm just going to rename my overriding methods to something other than save or delete etc. It's not particularly elegant and there is the risk of forgetting to use the custom method instead of the default one but oh well... – hubbabubba Oct 28 '17 at 18:55

2 Answers2

3

So after a lot of googling and so on, it doesn't seem possible to define for Maven's compiler which of the save methods is primary, the one in JpaRepository or the one in my custom repository. I don't know how the compiler used by Eclipse does it but clearly Maven doesn't follow the same logic here. That's a shame, because this way of coding custom methods and overrriding some JpaRepository methods would be the cleanest and nicest way. There is a @Primary annotation for determining which bean is primary for autowiring if several candidates exist, but there doesn't seem to be an equivalent solution for interface implementation methods. I haven't found any other way of doing this where I would not have to write any extra code. Extending SimpleJpaRepository class seems like a somewhat ugly solution since I would then have to make sure that implementation is used as a JpaRepository implementation.

So I've decided to solve this in a straight forward way:

public interface ReportRepository {
    public List<Report> findAll();

    public Report findOne(Long id);

    public void delete(Report report);

    public Report save(Report report) throws OptimisticLockException;

    public Long getReportCountForImporter(Long importerId);

    .... [other custom methods]

}

public interface ReportRepositoryDefaultMethods extends JpaRepository<Report, Long> {

}

public class ReportRepositoryImpl implements ReportRepository {

    @PersistenceContext()
    EntityManager entityManager;

    @Autowired
    ReportRepositoryDefaultMethods reportRepositoryDefaultMethods;

    public List<Report> findAll() {
        return reportRepositoryDefaultMethods.findAll();
    }

    public Report findOne(Long id) {
        return reportRepositoryDefaultMethods.findOne(id);
    }

    public void delete(Report report) {
        reportRepositoryDefaultMethods.delete(report);
    }

    @Transactional
    public Report save(Report report) throws OptimisticLockException {
        [custom implementation using entityManager methods]

    }
    .... [other custom methods]
}

It's not a neat solution since I have to include the default methods I use in my interface and its implementation, with just calls to the standard JpaRepository methods. But it works and the use of my ReportRepository interface is clean in that I don't have custom names for custom methods like customSave(), but the details of the implementation are hidden in the implementing class.

If anyone has a better solution, one with only minimal amount of code, I'd be interested to hear about it.

hubbabubba
  • 937
  • 1
  • 9
  • 17
0

In my case, I had a need to @PreAuthorize save method. I had to change it from

@Repository
public interface UserScoreRepo extends JpaRepository<UserScore, Long>, UserScoreRepoFrag {
    @PreAuthorize(value = "@roleValidatorService.UserScoreSave(#entity)")
    public <S extends UserScore> S save(UserScore entity);
}

to

@Repository
public interface UserScoreRepo extends JpaRepository<UserScore, Long>, UserScoreRepoFrag {
    @PreAuthorize(value = "@roleValidatorService.UserScoreSave(#entity)")
    public <S extends UserScore> S save(S entity);
}

HINT: notice the argument of save method.

Anand Rockzz
  • 6,072
  • 5
  • 64
  • 71