Wie testen Sie Spring @Transactional, ohne nur zu schlagen, hibernate level-1-cache oder mache ein manuelles session flush?
Mit Spring + Hibernate und transactional-Annotationen.
Ich versuche zum test das folgende:
- eine Methode aufrufen, die änderungen, die ein User-Objekt ruft dann eine
@Transactional
service-Methode beibehalten, es - gelesen das Objekt wieder aus der DB und sicherzustellen, dass es die Werte sind richtig, nach der Methode
Das erste problem, das ich hatte, war zu Lesen, das User-Objekt in Schritt 2 zurück, die man in der Hibernate-level-1-cache und wird nicht tatsächlich aus der Datenbank gelesen.
Ich daher manuell entfernt das Objekt aus dem cache mit der Sitzung zu zwingen, die aus der Datenbank gelesen. Allerdings, wenn ich dies tun, wird die Objekt-Werte sind niemals persistent im Gerät zu testen (ich weiß, er rollt wieder, nachdem der test abgeschlossen ist, weil die Einstellungen, die ich angegeben).
Ich habe versucht manuell Spülen der Sitzung nach Aufruf der @Transactional
service-Methode, und die, die es TATEN, übernehmen Sie die änderungen. Doch das war nicht was ich erwartet habe. Ich dachte, dass @Transactional
service-Methode würde versichern die Transaktion ein commit ausgeführt wurde und die session geleert, bevor es zurückgegeben.
Ich weiß, im Allgemeinen das Frühjahr wird entscheiden, Wann dies zu tun, management, aber ich dachte, die "unit of work" in @Transactional
Methode war die Methode.
In jedem Fall, jetzt versuche ich herauszufinden, wie ich den test ein @Transactional
Methode im Allgemeinen.
Hier ist eine junit-Testmethode, die fehlschlägt:
@RunWith(SpringJUnit4ClassRunner.class)
@Transactional
@TransactionConfiguration(transactionManager = "userTransactionManager", defaultRollback = true)
@WebAppConfiguration()
@ContextConfiguration(locations = { "classpath:test-applicationContext.xml",
"classpath:test-spring-servlet.xml",
"classpath:test-applicationContext-security.xml" })
public class HibernateTest {
@Autowired
@Qualifier("userSessionFactory")
private SessionFactory sessionFactory;
@Autowired
private UserService userService;
@Autowired
private PaymentService paymentService;
@Autowired
private QueryService queryService;
@Autowired
private NodeService nodeService;
@Autowired
private UserUtils userUtils;
@Autowired
private UserContext userContext;
@Test
public void testTransactions() {
//read the user
User user1 = userService.readUser(new Long(77));
//change the display name
user1.setDisplayName("somethingNew");
//update the user using service method that is marked @Transactional
userService.updateUserSamePassword(user1);
//when I manually flush the session everything works, suggesting the
//@Transactional has not flushed it at the end of the method marked
//@Transactional, which implies it is leaving the transaction open?
//session.flush();
//evict the user from hibernate level 1 cache to insure we are reading
//raw from the database on next read
sessionFactory.getCurrentSession().evict(user1);
//try to read the user again
User user2 = userService.readUser(new Long(77));
System.out.println("user1 displayName is " + user1.getDisplayName());
System.out.println("user2 displayName is " + user2.getDisplayName());
assertEquals(user1.getDisplayName(), user2.getDisplayName());
}
}
Wenn ich manuell Spülen Sie die session, dann ist der test erfolgreich ist. Allerdings hätte ich erwartet, dass die @Transactional
Methode, um Sorgfalt zu Begehen und Spülung der Sitzung.
Die service-Methode für updateUserSamePassword ist hier:
@Transactional("userTransactionManager")
@Override
public void updateUserSamePassword(User user) {
userDAO.updateUser(user);
}
Die DAO-Methode ist hier:
@Override
public void updateUser(User user) {
Session session = sessionFactory.getCurrentSession();
session.update(user);
}
SesssionFactory ist autowired:
@Autowired
@Qualifier("userSessionFactory")
private SessionFactory sessionFactory;
Ich bin mit der XML-Anwendung, die Kontext-Konfiguration. Ich habe:
<context:annotation-config />
<tx:annotation-driven transaction-manager="userTransactionManager" />
Und
<bean id="userDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${user.jdbc.driverClass}"/>
<property name="jdbcUrl" value="${user.jdbc.jdbcUrl}" />
<property name="user" value="${user.jdbc.user}" />
<property name="password" value="${user.jdbc.password}" />
<property name="initialPoolSize" value="3" />
<property name="minPoolSize" value="1" />
<property name="maxPoolSize" value="17" />
</bean>
<bean id="userSessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="userDataSource" />
<property name="configLocation" value="classpath:user.hibernate.cfg.xml" />
</bean>
<bean id="userTransactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="dataSource" ref="userDataSource" />
<property name="sessionFactory" ref="userSessionFactory" />
</bean>
Gibt es auch eine Komponente, scan auf die Dienste und dao-Klassen. Wie gesagt, das ist eine Arbeit in der Produktion.
Dachte ich, dass wenn ich eine Methode gekennzeichnet @Transactional
dass am Ende der Methode (z.B. die update-Methode hier), Frühling wäre gezwungen, die Sitzung zu Begehen und bündig.
Kann ich sehen, nur ein paar Optionen:
-
Ich etwas falsch konfiguriert, obwohl dies funktioniert für mich im Allgemeinen (nicht nur der unit-tests). Irgendwelche Vermutungen? Irgendwelche Ideen, wie man dies testen?
-
Etwas über das unit-test-config sich nicht verhält, die Art und Weise der app.
-
Transaktionen und Sitzungen, die nicht so arbeiten. Mein einziger Abzug ist, dass der Frühling ist die Transaktion verlassen wird und/oder die session öffnen nach dem aufrufen der update-Methode. Also, wenn ich Sie manuell entfernen Sie die Benutzer, die auf das Session-Objekt, die diese änderungen noch nicht festgeschrieben wurden noch.
Kann jemand bestätigen, ob dies ist das erwartete Verhalten? Sollte nicht @Transaction
gezwungen Begehen und bündig auf Sitzung? Wenn nicht, wie würde dann ein test-eine Methode gekennzeichnet @Transactional
und dass die Methoden, die eigentlich die Arbeit mit Transaktionen?
I. e., wie soll ich umschreiben, mein Unit-test hier?
Irgendwelche anderen Ideen?
Du musst angemeldet sein, um einen Kommentar abzugeben.
Hier ist, was ich lief in. Betrachten Sie diesen code in einer test-Methode:
Beachten Sie, dass die Methode userService.lesenuser gekennzeichnet ist @Transactional in der service-Klasse.
Wenn dieser test-Methode ist gekennzeichnet @Transactional der test schlägt fehl. Wenn es NICHT ist, gelingt es. Ich bin mir jetzt nicht sicher, ob/Wann der Hibernate-cache ist eigentlich immer beteiligt. Wenn die test-Methode transaktional ist dann jeder Lesen geschieht in einer Transaktion, und ich glaube, dass Sie nur Treffer, die den Hibernate-level-1-cache (und nicht tatsächlich aus der Datenbank gelesen). Allerdings, wenn der test-Methode NICHT transaktional ist, dann jeder Lesen geschieht in einem eigenen Transaktion, und jeder läuft die hit-Datenbank. So, der hibernate-level-1-cache ist gebunden an die Sitzungs - /Transaktions-management.
Take-aways:
Selbst wenn eine test-Methode ruft mehrere Transaktions-Methoden in eine andere Klasse, wenn das test-Methode selbst transaktional ist, werden alle diese Anrufe in einem Transaktion. Die test-Methode ist die "unit of work". Allerdings, wenn der test-Methode NICHT transaktional ist, dann wird jeder Aufruf einer transaktionalen Methode innerhalb der test führt innerhalb Ihrer eigenen Transaktion.
Meine test-Klasse markiert war, @Transactional, daher ist jede Methode werden die Transaktions-sofern nicht anders gekennzeichnet-mit einer übergeordneten annotation wie @AfterTransaction. Ich könnte genauso gut NICHT markieren Sie die Klasse, @Transactional und markieren Sie jede Methode, die @Transactional
Hibernate level-1-cache scheint gebunden an die Transaktion bei der Verwendung von Spring @Transactional. I. e. nachfolgende liest, der ein Objekt innerhalb der gleichen Transaktion auf der hibernate-Ebene-1-cache und nicht die Datenbank. Hinweis: es ist ein level-2-cache und andere Mechanismen, die Sie einstellen können.
Wollte ich haben @Transactional-test Methode dann verwenden Sie @AfterTransaction auf eine andere Methode in der test-Klasse und legt rohen SQL zu bewerten, Werte in der Datenbank. Dies würde die Umgehung der ORM und hibernate-level-1-cache insgesamt, versichern Sie wurden Vergleich der tatsächlichen Werte in die DB.
Die einfache Antwort war, einfach mit @Transactional aus meiner test-Klasse. Yay.
@Transactional
aber noch roll-back 🙁Q
Eine
Es ist das erwartete Verhalten. Der Frühling bewusst Transaktions-unit-test-Unterstützung durch design rollsback der Transaktion, nachdem der test abgeschlossen ist. Das ist by design.
Einer impliziten Transaktion Grenze erstellt wurde pro test (je Methode mit @Test) und sobald der test abgeschlossen ist, wird ein rollback gemacht wird.
Die Konsequenz daraus ist, nachdem alle tests abgeschlossen sind gibt es keine Daten, die tatsächlich geändert werden. Das ist ein Ziel, zu mehr "Einheit" mag, und weniger von einer "integration" mag. Lesen Sie die spring-Dokumentation, warum dies von Vorteil ist.
Wenn Sie wirklich wollen, um die test-Daten werden beibehalten und zur Anzeige, dass Daten außerhalb der Transaktion Grenze empfehle ich eine weitere Ende-zu-Ende-test wie ein functional/integration test wie Selen oder schlagen Sie Ihre externe WS/REST-API, wenn Sie eine haben.
Q
Eine
Ihre unit-test funktioniert nicht, weil Sie
session.entfernen
und nicht bündig. Nach dem entfernen wird dazu führen, dass änderungen nicht synchronisiert werden, obwohl Sie bereits genanntsession.update
Ihr in einer Transaktion und hibernate Chargen-Operationen. Dies ist mehr klar, mit raw-SQL als Ruhezustand verzögert beibehalten zu charge, bis alle Operationen, so wartet er, bis die session geleert wird, oder geschlossen, um die Kommunikation mit der Datenbank für die Leistung. Wenn Siesession.flush
obwohl der SQL ausgeführt werden, die sofort (ie-update wirklich kommt) und dann können Sie vertreiben, wenn Sie erzwingen möchten ein reread, aber Ihre Relektüre innerhalb der Transaktion. Ich bin mir eigentlich ziemlich sicher, dassflush
verursachen Räumung, so gibt es keine Notwendigkeit zu nennenevict
zu zwingen, noch einmal gelesen, aber ich könnte falsch sein.flush
die aktuelle Sitzung. Auch im Allgemeinen für die Prüfung der Werte in die Datenbank, die Sie verwenden sollten, eine einfache jdbc-Anweisung nicht der gleiche Mechanismus, wie Sie verwendet, um es in.rollback=false
wird nicht einen Unterschied machen. Die Transaktions-Grenze ist immer noch deine test-Methode. Die einzige Lösung ist, um Ihren test nicht transaktional, das gleiche Verhalten, wie in Ihrer web-Anwendung.@Transactional
angetroffen wird, in diesem Fall, dass auf den testcase (Sie setzen sich selbst!). Die Transaktion endet mit der gleichen Methode, die Schritte der Transaktion jede andere Methode nimmt an, dass die Transaktion (es sei denn, Sie haben eine REQUIRES_NEW oder NIE übertragungsgrad).