Spring Boot: save vom CrudRepository spichert nichts

Hallo zusammen,
ich krieg noch die Krise :smiley:

Ich hab eine Multitenancy-Funktion aus GitHub in mein Programm eingebaut. Aus irgendeinem Grund kann ich aber über save nichts mehr speichern. Die repository.save(myObj) wird einfach beim Debuggen ausgeführt, aber in der Datenbank kommt nichts an. Ich vermute diese Erweiterung aktiviert irgendwas, sodass ich mit Transactions arbeiten muss. Aber wie ?! Hat jemand ne Idee was solch ein Verhalten auslösen kann?

Hier eine (wichtige) Klasse, die die Datenbankverbindungen zu den Mandanten aufbaut. Mein Repository ist ein ganz normales und dieses wird @Autowired in den Controller geladen.

@GetMapping("/getBrand/{brandName}")
public Brand getBrand(@RequestHeader("tenant") String tenant, @PathVariable String brandName) {
	TenantContextHolder.setTenantId(tenant);
	Brand b = brandRepository.findByBrandName(brandName); << null, wenn Brand noch nicht existiert
	if (b == null) {
		Brand brand = new Brand();
		brand.setbrandName(brandName);
		Brand savedBrand = brandRepository.save(brand);  <<<wird nicht in DB gespeichert, kein Fehler

		return savedBrand; 
	}
	return b;
}

Edit: hier der Link zur Klasse bezgl. Tenants : /* * Copyright 2018 onwards - Sunit Katkar (sunitkatkar@gmail.com) * * Lic - Pastebin.com

Da der Controller bereits instantiiert ist und somit auch die DB Verbindung, wird sicherlich etwas in einer DB geschrieben, aber nicht in der du gerade den Eintrag erwartest.

Grund hierür ist, dass das auslesen der Header Tenant Variable vor dem Aufruf des Controllers passieren muss. Hier ist es vermutlich zu spät und sehr „anstrengend“, da jeder Controller diese Logik implementieren muss.

In dem anderen Beispiel dürfte ein Filter vor dem Controller den Tenant für den Thread zur Ausführung setzen.

Schöne Grüße
Martin

1 „Gefällt mir“

Hi Martin,

geschrieben wird nix, die Datenbanken bleiben leer. Was jedoch passiert; hibernate_sequence zählt die id hoch. Wenn ich mit Postman den BrandController Anfrage wird mir die Brand zurück gegeben, obwohl sie nicht geschrieben wurde. Hibernate vergibt aber eine ID. Rufe ich die selbe Brand wieder ab, erhöht sich die ID, weil die Brand nicht gefunden werden kann und nochmal „gespeichert“ wird (aber nicht ankommt)

Wenn ich manuell werte in de DB speichere und Abrufe, kommen die richtigen Werte aus der entsprechenden DB. Mit dem Filter Versuch ich mal :thinking: beim debuggen hat es jedoch die korrekte tenantID

Also nur falls du jetzt hier weitere Infos benötigst.

Dein Posting hat keine neuen Erkenntnisse gebracht.

Eigentlich ist das, was du an Quellcode gepostet hast das offensichtliche, dass man sich denken könnte.

Viel mehr benötigen wir alle Bean Konfiguration um das Fehlverhalten zu analysieren.

Mein dringender Ratschlag ist es die Sourcen alle zu Verfügung zu stellen, wenn du hier qualifizierte Antworten haben möchtest.

Sonst ist das hier nur allgemeines ins Blaue hinein.

Das nur so als Tipp, falls du hier weiter kommen möchtest.

Hi, ja das weiß ich, dass keine neuen Erkenntnisse drin waren:)

Da du so ziemlich sicher schriebst, dass es am fehlenden Filter liegt, wollte ich das erstmal testen. Der restliche Code ist hier zu finden, das ist Der Code den ich eingebunden hab https://github.com/sunitk/multitenancy-dynamic-tenant

Der Filter brachte auch nichts. Es kann nur etwas mit der oben verlinkten Klasse zu tun haben vermute ich.
https://pastebin.com/jbaC5Z0a

Da werden die Beans konfiguriert

Lass mal den ganzen Tentant-Kram weg, sodass er nur auf einer einzelnen Datenbank arbeitet - speichert er dann korrekt?
Laut dir schreibt er ja in die passende DB (zumindest hibernate_sequence) und aus der passenden wird gelesen, wenn du selbst was reinschreibst.

Lässt du SQL loggen? Zumindest irgendwas zu hibernate_sequence sollte dort zu sehen sein - und wenn ja: nur das?

Gibt es irgendwelche Exceptions?

Und ist dein gesamter Code irgendwo verfügbar?
Mit Teilstücken kann man da schlecht den Fehler finden, der kann überall versteckt sein…

Als ich diesen teanten Kram noch nicht drin hatte, schrieb es. Habe nun mal meine Entity auf IDENTITY gesetzt, sodass die DB nun mittels auto_increment die ID setzt, richtig?

Wenn ich manuell eine Brand eintrage und die abrufe, bekomme ich die korrekten Werte, samt meiner vergebenen ID. Meinen Code gibts leider nirgendwo. Könnte benötigtes aber posten, wenn ich weiß was genau

Dann wird nur folgendes Hibernatestatement ausgegeben (aber warum 2 mal?)

  select
        brand0_.id as id1_0_,
        brand0_.brand_name as brand_na2_0_ 
    from
        brand brand0_ 
    where
        brand0_.brand_name=?
Hibernate: 
    select
        brand0_.id as id1_0_,
        brand0_.brand_name as brand_na2_0_ 
    from
        brand brand0_ 
    where
        brand0_.brand_name=?

In der oben verlinkten Klasse, wird eine Bean erstellt:
@Bean(name = “tenantEntityManagerFactory”)
@ConditionalOnBean(name = “datasourceBasedMultitenantConnectionProvider”)
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
@Qualifier(“datasourceBasedMultitenantConnectionProvider”) MultiTenantConnectionProvider connectionProvider,
@Qualifier(“currentTenantIdentifierResolver”) CurrentTenantIdentifierResolver tenantResolver) {

	LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
	// All tenant related entities, repositories and service classes must be scanned
	emfBean.setPackagesToScan(new String[] { "com.domain.model.tenancy", "com.domain.model" });
	emfBean.setJpaVendorAdapter(jpaVendorAdapter());
	emfBean.setPersistenceUnitName("tenantdb-persistence-unit");
	Map<String, Object> properties = new HashMap<>();
	properties.put(org.hibernate.cfg.Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
	properties.put(org.hibernate.cfg.Environment.MULTI_TENANT_CONNECTION_PROVIDER, connectionProvider);
	properties.put(org.hibernate.cfg.Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantResolver);
	// ImprovedNamingStrategy is deprecated and unsupported in Hibernate 5
	// properties.put("hibernate.ejb.naming_strategy",
	// "org.hibernate.cfg.ImprovedNamingStrategy");
	properties.put(org.hibernate.cfg.Environment.DIALECT, "org.hibernate.dialect.MySQL5Dialect");
	properties.put(org.hibernate.cfg.Environment.SHOW_SQL, true);
	properties.put(org.hibernate.cfg.Environment.FORMAT_SQL, true);
	properties.put(org.hibernate.cfg.Environment.HBM2DDL_AUTO, "update");

	emfBean.setJpaPropertyMap(properties);
	LOG.info("tenantEntityManagerFactory set up successfully!");
	return emfBean;
}

Muss man diese irgendwie angeben mit @Transactional? in der Klasse, wo .save aufgerufen wird?

Alles.
Dinge weglassen führt üblicherweise dazu, dass auch die Fehlerquelle weggelassen wird.

Und welches wird beim („nicht-“) speichern eines noch nicht vorhandenen ausgegeben?

beim „nicht“-speichern wird das selbe ausgegeben, da ja erst geprüft wird ob schon vorhanden. das .save() an sich gibts nichts aus Einfach nichts…kein INFO, DEBUG einfach nichts :frowning: Ich werd mal schauen, dass ich den wichtigsten Code hochlade irgenwie.

22:45:46 DEBUG [http-nio-8182-exec-2] - 
    select
        brand0_.id as id1_0_,
        brand0_.brand_name as brand_na2_0_ 
    from
        brand brand0_ 
    where
        brand0_.brand_name=?
Hibernate: 
    select
        brand0_.id as id1_0_,
        brand0_.brand_name as brand_na2_0_ 
    from
        brand brand0_ 
    where
        brand0_.brand_name=?
22:45:46 DEBUG [http-nio-8182-exec-2] - committing
22:45:46 DEBUG [http-nio-8182-exec-2] - begin
22:45:46 DEBUG [http-nio-8182-exec-2] - committing

Problem behoben:

In der folgenden Methode musste ein Qualifier angegeben werden, nun klappt es. Was auch immer das zu bedeuten hat, muss ich erstmal lernen. Ich danke euch allen wieder für eure tatkräftige Unterstützung! Eine Spendenfunktion gibt es in diesem Forum nicht oder?

@Bean(name = "tenantTransactionManager")
public JpaTransactionManager transactionManager(@Qualifier("tenantEntityManagerFactory") EntityManagerFactory tenantEntityManager) {
	JpaTransactionManager transactionManager = new JpaTransactionManager();
	transactionManager.setEntityManagerFactory(tenantEntityManager);
	return transactionManager;
}

Edit: noch eine unabhängige Frage von dem Problem. @GeneratedValue(strategy = GenerationType.IDENTITY)
oder AUTO? Identity empfinde ich als schneller, der hat AUTO irgendwelche Vorteile die ich noch nicht kenne?

Transaktionen sind per DB Connection, wenn da mehrere offen sind, und das ist immer der Fall, ohne tenants wegen pooling, mit Tenants dann pooling und per tenant (Tenants haben ja ihre eigene DB), wenn die Transaktionen nicht committed wird, wird nix gespeichert, entweder es kommt nix in der DB an (performance Optimierung), oder the DB bekommt alle SQL statements aber eben kein commit.

Wenn man mit JDBC und RDBMS nicht vertraut ist, sind ORMs nur verwirrend.

Zu „empfinden“ ist immer wichtig, ausser wenn es um performance geht, dann zaehlt nur messen, wenn du nicht weisst was der Unterschied zwischen AUTO und IDENTITY ist, konzentriere dich erstmal darauf :wink:

1 „Gefällt mir“