0

I got an entity B bound by a ManyToOne with another Entity A. Writing add method in its controller I obviously pass a field of an Instance of A as a parameter, from which I select the whole object to extract its ID that I use as the key for the relationship. If the instance of A I'm trying to select is non-existent, I want it to be created (the field I'm passing as a parameter is enough for its constructor). Can I do that without instantiating Controller A inside Controller B? It seems weird...

UPDATE I rewrote most part of my project, thanks to the hints by @Artur Vakhrameev

I created a service for every entities and instantiated one of them inside the other one. This is the service for the "many side" relationship entity

@Transactional
@Service
public class LineaService {

    @Autowired
    private LineaRepository lineaRepo;
    @Autowired
    private MarcaService marcaService;

    public Linea create(Linea linea) {
        Linea nuovaLinea = new Linea();
        nuovaLinea.setNome(linea.getNome());

        if (marcaService.findByName(linea.getMarca().getNome()) == null)
            nuovaLinea.setMarca(marcaService.create(linea.getMarca()));
        else
            nuovaLinea.setMarca(linea.getMarca());

        lineaRepo.save(nuovaLinea);

        nuovaLinea.getMarca().addLinea(nuovaLinea);

        return nuovaLinea;
    }

And this is now the simple add postmapping in my controller, stripped of all the business logic

@PostMapping("/add")
    public String aggiungiLinea(Linea linea) {
        lineaService.create(linea);
        return "redirect:list";
    }

UPDATE 2 (this one seems to work as expected)

public Linea create(Linea linea) {
    Marca marcaAssociata = marcaService.findByName(linea.getMarca().getNome());
    if (marcaAssociata == null) 
        linea.setMarca(marcaService.create(linea.getMarca()));
    else
        linea.setMarca(marcaAssociata);

    return lineaRepo.save(linea);
}
4javier
  • 481
  • 2
  • 7
  • 22

1 Answers1

0

If i understand you correctly:

  1. You don't need a controller A inside B. Controllers does not have responsoblity to save entities, they are only about web exchange. You can inject, for example, your @Repository or @Service component of A entity to your B controller and call a save() method for your entities. (better approach, in my opinion, will be to inject AResporitory inside BService class)

    UPDATED

    Example:

    @Transactional
    @Service
    public class LineaService {
    
    @Autowired
    private LineaRepository lineaRepo;
    @Autowired
    private MarcaService marcaService;
    
    public Linea create(Linea linea) {
        //creates new marca if not exists
        //no need for this block if you add cascade insert to your entity
        if (linea.getMarca().getId() == null) {
            marcaService.create(linea.getMarca());
        }
    
        return lineaRepo.save(linea);
    }
    

    If you set an existing marca value, you found it before, so in that case it's Id will have some value (user chose it on UI form, for example).

  2. Another approach will be using cascade inserts in your B entitiy.

    @Entity
    public class EntityB {
    
        @ManyToOne(cascade = CascadeType.PERSIST)
        protected EntityA entityA;
    
    }
    

UPDATED Cascades does'nt create new rows of already existings, but Hibernate checks row's existence only by primary key. If your entity object has empty primary key it means that it is a new object and yout need to insert a new row. In many cases controller will recieve a filled marca's Id field, because as i wrote before, you user can select it on UI form using, for example, a some kind of "select component". But if in your case, user can't select a marca, and only can type it's name, then, of course, cascades won't be a good solution and u can search marca by nome firstly (as you do in your code).

Artur Vakhrameev
  • 794
  • 7
  • 11
  • Thanks for your reply. The second approach looks simply and clean, I'm reading something about it. About your first proposal, even if I inject _A repo_ inside _B controller_ to use `repositoryA.save(entity)`, I would have no access to _A factory method_ anyway, then I could not create an entity of A class to pass to `repositoryA.save()`. Am I missing something? – 4javier Jan 16 '19 at 11:34
  • What a factory method are you talking about? Don’t you get your A entity from request using ObjectMapper? – Artur Vakhrameev Jan 16 '19 at 12:20
  • ObjectMapper? If you're talking about Jackson, I don't use json at all. – 4javier Jan 16 '19 at 13:45
  • After your replies I realized how many basic skills I was lacking and took my time to study. Now I rewrote my code as you can see into the update to the post. I added a couple services and moved there all the business logic that previously lied into controllers. I still not use a mapper, but I think I know now what you were talking about. I think for my simple entities these methods are enough. If you consider my code still dirty and not compliant with some patterns, please let me know. Any correction and link to docs is welcome. Otherwise I'll mark your first reply as the right one. – 4javier Jan 20 '19 at 15:10
  • Your code looks clear for me mostly, but why are creating a new `Linea` object on your create method, can't you use the same? I wrote a simple example of my vision, but of course it may be incorrect and i misunderstand something. For exmaple, if you need to keep unsaved linea object in memory for some reasons. I also check marca's existing by `Id` field. That means checking by entity's primary key, so if your `Nome` field is a PK everything ok, except `Id` is more suitable name for PK, in my opinion. – Artur Vakhrameev Jan 20 '19 at 19:17
  • - You're right about uselessness of the new Linea object.(But you still pass it to lineaRepo.save. Typo?) - I cannot check on Marca.id, because the parameters taken by this method are passes by a form, where the user input just linea.marca.nome and linea.nome, thus that check would result always in a null. - You're wrong about the cascade on the ManyToOne side of the relationship: the way you proposed turns it into an unidirectional OneToOne, making hibernate create a new Marca row for every Linea row regardless of the fact that its Marca already exists. See my new update. – 4javier Jan 21 '19 at 11:57
  • Cascades does'nt create new rows of already existings, but Hibernate checks row's existence only by primary key. If your entity object has empty primary key it means that it is a new object and yout need to insert a new row. In many cases controller will recieve a filled marca's Id field, because as i wrote before, you user can select it on UI form using, for example, a some kind of "select component". But if in your case, user can't select a marca, and only can type it's name, then, of course, cascades won't be a good solution and u can search marca by nome firstly (as you do in your code). – Artur Vakhrameev Jan 21 '19 at 12:33
  • Perfect. Thanks for the explanation. If you add this clarification to your original answer, I'll mark it as the accepted one. P.S. I'm now writing the service for a more complex entity, do you get a good link to mappers pattern? I'm considering to use it. – 4javier Jan 21 '19 at 13:40
  • Thank you for accepting the answer. What kind of mappers are you talking about? Maybe something like [dozer](http://dozer.sourceforge.net/) is what are looking for? But anyway, i think, you can detailly exmplain what you in another question. – Artur Vakhrameev Jan 21 '19 at 15:07
  • I was thinking of a simple handmade implementation from scratch like this one (https://github.com/pkainulainen/spring-data-jpa-examples/blob/master/custom-method-single-repo/src/main/java/net/petrikainulainen/springdata/jpa/todo/TodoMapper.java) rather than an external library. I'll try to find out which pattern it refers to. Thanks again for all your help. – 4javier Jan 21 '19 at 15:16
  • take a look a [this](https://stackoverflow.com/questions/1051182/what-is-data-transfer-object) – Artur Vakhrameev Jan 21 '19 at 15:23