CrudRepository alleine?

werdas34

Bekanntes Mitglied
Hallo,

erstmal vorweg: Ich habe erst paar Wochen Spring Boot Erfahrung und deren Konzepte.
Wenn jemand diesbezüglich Tipps, gute Quellen etc kennt, gerne her damit.

Kann man CrudRepository direkt verwenden ohne eine extra Klasse zu haben, die von CrudRepository erbt?
CrudRepository kann soweit ich das verstehe nicht als Bean fungieren, aber selbst mit Constructor Injection funktioniert es nicht, oder?
Wenn man nur die Defaultmethoden verwenden möchte?

Ich frage hier bewusst so deutlich, da ich in meinen ersten Beispielen mit der extra Repository Klasse, die von CrudRepository erbt, nie andere Methoden definiert habe.
Da sowohl meine extra Klasse wie auch CrudRepository ein Interface sind, dachte ich das sollte gehen.

Ich habe gestern das ausprobiert nur mit CrudRepository und siehe es funktioniert.
Heute ein neues Projekt angefangen und siehe es funktioniert nicht.
Gestern hat es wohl deswegen funktioniert, da ein passendes Bean für die Main Klasse instanziiert wurde und dann in meinen Controller gewandert ist.

Kann man CrudRepository alleine verwenden?
Oder muss man wirklich immer eine neue Klasse erstellen selbst wenn man keine Custom Methoden verwenden möchte?
 

Marinek

Bekanntes Mitglied
Nein, kann man nicht.

Es muss ein AJP Proxy erstellt werden. Der component scan sucht also im classpath nach Ableitungen des Interfaces CrudRepository.

Du musst ja auch CrudRepository mindestens hinsichtlich der Generics parametrisieren, damit die CrudRepo weiß, was es mappen soll.
 

werdas34

Bekanntes Mitglied
Super, dann weiß ich jetzt das es nicht geht.

Du musst ja auch CrudRepository mindestens hinsichtlich der Generics parametrisieren, damit die CrudRepo weiß, was es mappen soll.

Das stimmt. Wenn ich aber den Constructor verwende, kann ich das Ganze ohne extra Klasse parametrisieren.
Java:
    private CrudRepository<User, Long> userRepository;
    
    public UserController(CrudRepository<User, Long> userRepository) {
        this.userRepository = userRepository;
    }
 

KonradN

Super-Moderator
Mitarbeiter
Vielleicht zeigst Du uns einfach einmal, was ging und was dann nicht ging. Dann kann man ggf. mehr sagen. Im Augenblick verstehe ich nicht wirklich, was Du z.B. mit
Ich habe gestern das ausprobiert nur mit CrudRepository und siehe es funktioniert.
Heute ein neues Projekt angefangen und siehe es funktioniert nicht.
Gestern hat es wohl deswegen funktioniert, da ein passendes Bean für die Main Klasse instanziiert wurde und dann in meinen Controller gewandert ist.
meinst.
 

werdas34

Bekanntes Mitglied
Habe ich noch nie ausprobiert... Geht das so?
Funktioniert nicht. Also ich habs nicht zum laufen bekommen. Deswegen bin ich so verwirrt.

@KonradK
Das folgende Beispiel hat funktioniert.
Java:
@SpringBootApplication
public class DemoApplication {
        public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
    
    @Bean
    public CommandLineRunner demo( CrudRepository<Customer, Long> repository) {
        return (args) -> {
          // save a few customers
          repository.save(new Customer("Jack", "Bauer"));
          repository.save(new Customer("Chloe", "O'Brian"));
          repository.save(new Customer("Kim", "Bauer"));
          repository.save(new Customer("David", "Palmer"));
          repository.save(new Customer("Michelle", "Dessler"));
        };
      }
}

@RestController
public class CustomerController {
    @Autowired
    private CrudRepository<Customer, Long> repository;
    
    /*public CustomerController(repository repository) {
        this.repository = repository;
    }*/
    
    @GetMapping("/customer")
    public ResponseEntity<List<Customer>> getAllCustomer() {
        List<Customer> customers = (List<Customer>) this.repository.findAll();
        return new ResponseEntity<>(customers, HttpStatus.OK);
    }
}

@Entity
@Table(name = "Customers")
public class Customer {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    private String firstName;
    private String lastName;

    protected Customer() {}

    public Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    // getter setter etc
}

public interface CustomerRepository extends CrudRepository<Customer, Long> {
}

Das funktioniert. (Ich hoffe es zumidenst, ist gekürzt) Ich habe das Interface CustomerRepository erstellt, verwende es aber nirgends. Dennoch funktioniert es.

Und ich habe jetzt in diesem Projekt nur das CustomerRepository gelöscht. Nun erhalte ich die Fehlermeldung:
Action:
Consider defining a bean of type 'org.springframework.data.repository.CrudRepository' in your configuration.

Ich verstehe grade nicht wie das mit CustomerRepository funktioniert. Soweit ich das verstehe wird ein Bean CustomerRepository erstellt und dort injektiert, wo der Datentyp CustomerRepository passt.
Offensichtlich muss es nicht exakt der Typ CustomerRepository sein sondern es reicht wenn der passende Typ die Oberklasse (CrudRepository) ist
Vermutlich kann er es zuordnen, da CrudRepository durch die Typen (<Customer, Long>) eindeutig ist.

Scheint wirklich so zu sein wie ich es oben beschreibe, denn wenn ich ein zweites Interface erstelle mit extends CrudRepository<Customer, Long>, dann bekomme ich die Meldung:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

Ist für den Anfang schon bisschen verwirrend, wenn plötzlich ein Datentyp eingesetzt wird, denn man so nicht erwartet.
Dachte Beans funktionieren nur mit exakten Datentypen..

Dennoch verstehe ich nicht weshalb das Constructor Jnjection nicht funktioniert. Das Repository muss ja nicht zwingend asl Bean fungieren..
Code:
    private CrudRepository<User, Long> userRepository;
    
    public UserController(CrudRepository<User, Long> userRepository) {
        this.userRepository = userRepository;
    }
 

Oneixee5

Top Contributor
Einfach nur CrudRepository als Datentyp zu verwenden ist ein Ansatz, welcher sowieso nicht funktioniert. Du hast in dem Moment nur die Methoden von CrudRepository zur Verfügung. Im CustomerRepository definierst du aber noch weitere Abfragen wie findCustomerByName etc. Diese hast du nicht zu Verfügung wenn du CrudRepository als Datentyp verwendest. Die Diskussion ist also sinnlos theoretisch.
 

werdas34

Bekanntes Mitglied
Wenn man Custom Abfragen, wie findCustomerByName, machen möchte, stimme ich dir vollkommen zu.
Möchte ich dagegen nur die klassischen Methoden verwenden, wie save, findById, existById, findAll, dann sollte der normale Datentyp ausreichen.
 

Marinek

Bekanntes Mitglied
Dependency Injection funktioniert so, dass beim Zusammenstellen einer Bean (e.g. hier dein UserController) geschaut wird, welche Bean wird hier benötigt und gibt es diese im Application Context.

Eine Bean muss zunächst erstellt werden und dem Application Context bekannt gemacht werden, damit diese Injected werden kann.

Spring sucht dazu im Classpath nach Implementierungen des Interfaces und erstellt dann eine Implementierung dessen via AJP Proxys. Dazu gehören auch weitere Abhängigkeiten, wie zum Beispiel ein PersitenceContext und PersistenceManager; Diese haben wieder spezielle Abhänigkeiten z.B. Hibernate Objekte und ORM.

Du formulierst den Bedarf, aber da es keine Entsprechung im Application Context gibt, kann hier nix injected werden. Es wird auch nicht beim Erstellen gemacht, sondern eben davor.

Daher funktioniert das so, wie du schreibst nicht.

Um die generischen Methoden zu nutzen, kannst du eine eigene Implementierung der CrudRepo besteuern, die auf diese Weise injected werden kann.

---
Offtopic: Das ist echt elegant gelöst und man kann damit sehr viel machen. Es lohnt sich in die Implementierung der CrudRepo und der Erstellung des AJP reinzuschauen, um dies für seine eigenen Projekte Nutzbar zu machen.
 

werdas34

Bekanntes Mitglied
Dass das im Vorfeld bekannt sein muss und das dem Interface für das Generic die Datentypen bekannt sein müssen, verstehe ich.

Aber wenn ich das mit dem Konstruktor mache, dann gebe ich ihm die Datentypen für das Generic. Und ich möchte nicht das Spring etwas injected, sondern einfach das Objekt erzeugt.
Spring bzw der Application Context möchte was injecten, ohne das ich es will.
Java:
private CrudRepository<User, Long> userRepository;
public UserController(CrudRepository<User, Long> userRepository) {
    this.userRepository = userRepository;
}

Es kann und wird wahrscheinlich an meinen begrenzten Spring Wissen liegen.
So wie ich Beans verstanden habe, wird es nur dort injected, wo ich es ihm erlaube. Z.B.:
Java:
@Autowired
private CrudRepository<User, Long> userRepository;
Dort gebe ich Spring die Möglichkeit zu injecten. Bei meinem Beispiel mit dem Konstruktor gebe ich ja bereits alles vor. Da gibt es nichts zum injecten. Dennoch wird mir sozusagen das aufgezwungen..
 

mrBrown

Super-Moderator
Mitarbeiter
Ich glaube du hast ein bisschen missverstanden, was „Injecten“ in dem Kontext meint. Das kommt von „Dependency Injection“ und ist als Pattern völlig unabhängig von Spring; das meint nur, dass Dependencies deiner Klasse irgendwie von außen übergeben werden (zB über Konstruktor, Setter, Felder), und die Klasse sie sich nicht selbst holt (zB über Singleton Pattern, new).


Ob du das als Konstruktor-Parameter oder als Feld mit @Autowired angibst, bedeutet da das gleiche: im das Objekt vollständig zu erstellen, muss diese Abhängigkeit bereitgestellt werden.


Und als Abhängigkeit bereitstellen kann Spring nur Beans, die den Application Context auf irgendeinem Weg bekannt gemacht wurden. ZB mit einer der @Component-Annotationen, mit @Bean oder eben indem man ein Interface von CrudRepository ableistet, in dem Fall erstellt Spring selbst eine Klasse, die das Interface implementiert.
 

Marinek

Bekanntes Mitglied
Glaube ich beginne zu verstehen, was du meinst.

Nein, spring wird alle passenden beans dort einfügen. Wenn du also zwei Implementierungen des Interfaces A hast, und beide befinden sich als Instanz im Application Context, dann wirst du sagen müssen, welche Bean du meinst. Oder du machst sowas wie List<A>. Oder Map<String, A>. Dann injected spring alle Implementierungen und im Falle der map auch mir Ihren eindeutigen Namen.

Wie dem auch sei: wenn du einen constructor hast , dann sagst du: So und nicht anders soll meine Bean aufgebaut werden.

Lässt du nun durch den DI Container die Bean erstellen, so wird er die passende Bean suchen.

Dann macht der constructor auch keinen Sinn, irgend wie anders zu verwenden, weil du ihn gerade dazu benutzt eine Bean zu bauen, über den DI Container.

Möchtest du dies nur als variable ohne di nutzen, musst du einen alternativen constructor anbieten und so das injection optional machen oder gar nicht erst über den DI Container bereitstellen.
 

LimDul

Top Contributor
Hinzu dürfte noch ein Problem mit Generics kommen.

Kompiliert sieht der Code
Java:
public Constructor(CrudRepository<Customer, Long> repo) {
nämlich so aus
Java:
public Constructor(CrudRepository repo) {

Die Type Informationen sind zur Laufzeit nicht mehr vorhanden. Leitet man ein Interface ab und definiert dort die Typen, so sind sie dann zur Laufzeit vorhanden.
 

werdas34

Bekanntes Mitglied
Und als Abhängigkeit bereitstellen kann Spring nur Beans, die den Application Context auf irgendeinem Weg bekannt gemacht wurden. ZB mit einer der @Component-Annotationen, mit @Bean oder eben indem man ein Interface von CrudRepository ableistet, in dem Fall erstellt Spring selbst eine Klasse, die das Interface implementiert.
Wie dem auch sei: wenn du einen constructor hast , dann sagst du: So und nicht anders soll meine Bean aufgebaut werden.
Das heißt, dass alles in Spring irgendwo ein Bean ist. Entweder als Service oder Component oder sonst was?

Ich habe z.:B. in meinem aktuellen Experiment eine Model-Klasse Team:
Java:
@Entity
public class Team {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    
    @Column   
    private String name;
    
    @ManyToOne(cascade = CascadeType.ALL)
    private User user;
    
    // Constructor, getter and setter...
}
Die hat bisher auch immer funktioniert. Ich habe mir die Doc Eintrag zu @Entity durchgelesen und dort stand nichts von Bean. Wenn ich diese Annotation ausklammere, dann bekomme ich die Fehlermeldung:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'teamController': Unsatisfied dependency expressed through field 'teamRepository': Error creating bean with name 'teamRepository' defined in com.example.DBrecap.service.TeamRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Not a managed type: class com.example.DBrecap.model.Team


Oder wenn ich das Beispiel von oben aufgreife mit CustomerRepository und CrudRepository.
CrudRepository hat die Annotation @NoRepositoryBean, was erklärt warum es nicht als Bean erstellt werden kann.
CustomerRepository ohne irgendeine Annotation funktioniert als Bean.
Also werden auch Beans erstellt ohne entsprechende Annotation?
Java:
// kein @Service oder so
public interface TeamRepository extends CrudRepository<Team, Long>{
}


Ich merke aber schon ich muss mich mehr mit den Grundlagen beschäftigen und dann erst mit den anderen Themen.
Habe jetzt auch vom Bean life cycle gehört.
Also eine Frage an die Experten: Wie sollte ein Spring Beginner anfangen? Welche Kurse, welche Bücher, welche Quellen sind empfehlenswert? Welche Themen zuerst? Wie habt ihr angefangen?
Ich habe jetzt GeeksforGeeks Spring Boot Roadmap gefunden und würde jetzt mit dem Thema Spring Core anfangen.
Und dann würde ich mich mit Web und Database weitermachen.
 

LimDul

Top Contributor
Nicht alles ist eine Bean. Entitäten sind keine (und sollte man auch nicht zu einer machen, den Spaß hatten wir hier im Projekt ...)

Aber alles was per Dependency Injection kommt muss eine Bean sein. (Also alles, was mit AutoWired annotiert ist, bzw. Konstruktor Parameter, wenn die Klasse selber eine Bean ist)

Ganz ganz früher musste man in irgendwelchen XML Konfigurationen angeben, welche Klasse eine Bean ist. Das wurde im Laufe der Zeit immer komfortabler. Der Nachteil ist allerdings, dass auch immer mehr "Magie" hinzugekommen ist. Spring Boot arbeitet mittels convention over configuration, das heißt viele Dinge muss man nicht explizit konfigurieren, sondern sind implizit über Konventionen festgelegt.

Das man z.B. an die Ableitungen des Interfaces CrudRepository keine @Service oder ähnliches dran schreiben muss, liegt genau an diesem Konzept. Es gibt die Konvention, wenn es eine Ableitung eines solchen Interfaces gibt, dann macht Spring Boot folgendes:
  • Erstellt dynamisch eine Implementierung dieses Interfaces
  • Diese Implementierung wird als Bean im ApplicationContext implementiert

Das passiert alles im Hintergrund beim Start der Anwendung bzw. sobald die Implementierung benötigt wird.
 

Neue Themen


Oben