I am using Postgres 9.2, hibernate 4.3.0 final.
I have testClass:
#Entity
#Table(name="testClass")
public class testClass implements Serializable {
#Id
#Column(name = "id")
private Integer id;
#Column(name="name")
private String name;
public Integer getId() {
return id;
}
}
Created from another class's method:
try {
new Configuration().configure("/hibernate.cfg.xml");
new testClass();
} catch(Exception e) {
System.out.println(e);
}
Here is my hibernate.xml.cfg:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory name="postgres">
<property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
<property name="hibernate.connection.password">123</property>
<property name="hibernate.connection.url">jdbc:postgresql://localhost:5432/postgres</property>
<property name="hibernate.connection.username">postgres</property>
<property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
<property name="hbm2ddl.auto">create</property>
<mapping class="testClass"/>
</session-factory>
</hibernate-configuration>
It executes on jboss server side:
[2014-01-06 05:59:01,592] Artifact server:ejb: Artifact is deployed successfully
17:59:22,880 INFO [org.hibernate.cfg.Configuration] configuring from resource: /hibernate.cfg.xml
17:59:22,881 INFO [org.hibernate.cfg.Configuration] Configuration resource: /hibernate.cfg.xml
17:59:22,889 INFO [org.hibernate.cfg.Configuration] Configured SessionFactory: postgres
But nothing happens :(
I'm checking for new tables in my PostgresDB but there's nothing.
What did I miss?
What did you expect to happen?
You create a new empty entity, then you exit.
You don't persist() the entity with an entity manager (or in Hibernate terms, save() to a Session). So it never exists as far as the database is concerned. It is just a plain Java object like any other, and gets garbage collected when the last reference to it is removed.
You need to:
Use the Configuation to produce a SessionFactory and store the SessionFactory somewhere accessible. You don't want to create this all the time, it should be created on startup. Container-managed persistence and injection can be handy here.
Obtain a Session from the SessionFactory
Pass the new object to Session.save(...), so it gets INSERTed in the DB after proper key generation, etc.
It might be a good idea to re-read a Hibernate and/or JPA tutorial to cover the basics of the object life cycle. The Getting Started Guide may be a good starting point, particularly the section on native Hibernate APIs.
Personally, if I was doing basic stuff I'd use the JPA APIs instead, though. PersistenceUnit, EntityManager, etc. See Getting started with Hibernate and JPA.
Related
(Please note : while investigating on this issue I better spotted the source of problem that I introduce here)
I am very new to Hibernate and SpringBoot. My project deals with a search engine which indexing (javafx client) and searching (web client) parts are separated. The web client uses SpringBoot and during the process of the user's request, I need to retrieve information on the Solr Index to search against. These information are stored in a Hibernate / H2 local database where I use the following Entity class :
#Entity
#Table(name = "IndexSetups")
#Access(AccessType.PROPERTY)
public class IndexSetup {
private final SimpleIntegerProperty id = new SimpleIntegerProperty();
#Id
#GeneratedValue(strategy = GenerationType.AUTO) // For H2 AUTO is required to auto increment the id
public int getId() {
return id.get();
}
//... other properties, getters and setters
}
A LocalDatabase class features a method to list all IndexSetups from the DB (there are also other methods but focusing on this one is enough to understand the issue). I need to call it in a controller but I get a "Not an entity : class IndexSetup".
However the only way to use my Entity is to prevent the launching of SpringBoot (ie commenting the line starting with SpingApplication.run(...)) :
#SpringBootApplication
public class ServerApplication {
public static void main(String[] args) {
// SpringApplication.run(ServerApplication.class, args);
ArrayList<IndexSetup> availableSearchIndex =
LocalDatabase.getInstance(false) // no GUI
.getAllIndexSetups();
// The list is displayed as long as SpringApplication.run() call is commented. Otherwise "Not an entity"
System.err.println("The Index setups are " + availableSearchIndex);
}
}
Of course this is not a solution since I do need to process the user's request via Spring Boot :-D!
Finally the hibernate.cfg.xml file reads :
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">org.h2.Driver</property>
<!--The db file resides wihtin the parent project (so one level above the calling project see ../)-->
<!--We use the auto server mode to be able to open the database from server and from indexer simultaneously
see http://www.h2database.com/html/features.html#auto_mixed_mode -->
<property name="connection.url">jdbc:h2:file:../MyAppDB;DB_CLOSE_ON_EXIT=TRUE;AUTO_SERVER=TRUE</property>
<property name="connection.username">test</property>
<property name="connection.password"/>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
<!-- Disable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">false</property>
<!-- Updates the existing database schema on startup -->
<property name="hbm2ddl.auto">update</property>
<!-- The mapping information of entities -->
<mapping class="my.package.Entities.IndexSetup"/>
</session-factory>
</hibernate-configuration>
The stack trace is :
Exception in thread "restartedMain" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: java.lang.IllegalArgumentException: Not an entity: class my.package.Entities.IndexSetup
at org.hibernate.metamodel.internal.MetamodelImpl.entity(MetamodelImpl.java:457)
at org.hibernate.query.criteria.internal.QueryStructure.from(QueryStructure.java:126)
at org.hibernate.query.criteria.internal.CriteriaQueryImpl.from(CriteriaQueryImpl.java:153)
at my.package.LocalDatabase.getAllIndexSetups(LocalDatabase.java:99)
at my.package.ServerApplication.main(ServerApplication.java:15)
And I've just noticed those two lines printed by Spring Boot at startup :
2018-10-15 11:47:46.208 INFO 27730 --- [ restartedMain] org.hibernate.cfg.Environment : HHH000206: hibernate.properties not found // Maybe this one is not just "INFO"
2018-10-15 11:47:46.591 WARN 27730 --- [ restartedMain] org.hibernate.orm.connections.pooling : HHH10001002: Using Hibernate built-in connection pool (not for production use!)
2018-10-15 11:47:46.594 INFO 27730 --- [ restartedMain] org.hibernate.orm.connections.pooling : HHH10001005: using driver [org.h2.Driver] at URL [jdbc:h2:file:../MyAppDB;DB_CLOSE_ON_EXIT=TRUE;FILE_LOCK=NO] // Just to show that my bibernate.cfg.xml is found
It looks unlikely to be a dependency problem as it only stops working when Spring application is run.
So my question is : what should I do to make my Entity work within SpringBoot ?
Any help much appreciated!
Add #EntityScan to ServerApplication.class.
#EntityScan (org.springframework.boot.autoconfigure.domain.EntityScan) identifies which classes should be used by a specific persistence context.
We have a Service which is #Stateful. Most of the Data-Operations are atomic, but within a certain set of functions We want to run multiple native queries within one transaction.
We injected the EntityManager with a transaction scoped persistence context. When creating a "bunch" of normal Entities, using em.persist() everything is working fine.
But when using native queries (some tables are not represented by any #Entity) Hibernate does not run them within the same transaction but basically uses ONE transaction per query.
So, I already tried to use manual START TRANSACTION; and COMMIT; entries - but that seems to interfere with the transactions, hibernate is using to persist Entities, when mixing native queries and persistence calls.
#Stateful
class Service{
#PersistenceContext(unitName = "service")
private EntityManager em;
public void doSth(){
this.em.createNativeQuery("blabla").executeUpdate();
this.em.persist(SomeEntity);
this.em.createNativeQuery("blablubb").executeUpdate();
}
}
Everything inside this method should happen within one transaction. Is this possible with Hibernate?
When debugging it, it is clearly visible that every statement happens "independent" of any transaction. (I.e. Changes are flushed to the database right after every statement.)
I've tested the bellow given example with a minimum setup in order to eliminate any other factors on the problem (Strings are just for breakpoints to review the database after each query):
#Stateful
#TransactionManagement(value=TransactionManagementType.CONTAINER)
#TransactionAttribute(value=TransactionAttributeType.REQUIRED)
public class TestService {
#PersistenceContext(name = "test")
private EntityManager em;
public void transactionalCreation(){
em.createNativeQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','c')").executeUpdate();
String x = "test";
em.createNativeQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','c','b')").executeUpdate();
String y = "test2";
em.createNativeQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('c','b','a')").executeUpdate();
}
}
Hibernate is configured like this:
<persistence-unit name="test">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<jta-data-source>java:jboss/datasources/test</jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<property name="hibernate.transaction.jta.platform"
value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform" />
<property name="hibernate.archive.autodetection" value="true" />
<property name="hibernate.jdbc.batch_size" value="20" />
<property name="connection.autocommit" value="false"/>
</properties>
</persistence-unit>
And the outcome is the same as with autocommit mode: After every native query, the database (reviewing content from a second connection) is updated immediately.
The idea of using the transaction in a manual way leads to the same result:
public void transactionalCreation(){
Session s = em.unwrap(Session.class);
Session s2 = s.getSessionFactory().openSession();
s2.setFlushMode(FlushMode.MANUAL);
s2.getTransaction().begin();
s2.createSQLQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','c')").executeUpdate();
String x = "test";
s2.createSQLQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','c','b')").executeUpdate();
String y = "test2";
s2.createSQLQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('c','b','a')").executeUpdate();
s2.getTransaction().commit();
s2.close();
}
In case you don't use container managed transactions then you need to add the transaction policy too:
#Stateful
#TransactionManagement(value=TransactionManagementType.CONTAINER)
#TransactionAttribute(value=REQUIRED)
I have only seen this phenomenon in two situations:
the DataSource is running in auto-commit mode, hence each statement is executed in a separate transaction
the EntityManager was not configured with #Transactional, but then only queries can be run since any DML operation would end-up throwing a transaction required exception.
Let's recap you have set the following Hibernate properties:
hibernate.current_session_context_class=JTA
transaction.factory_class=org.hibernate.transaction.JTATransactionFactory
jta.UserTransaction=java:comp/UserTransaction
Where the final property must be set with your Application Server UserTransaction JNDI naming key.
You could also use the:
hibernate.transaction.manager_lookup_class=org.hibernate.transaction.JBossTransactionManagerLookup
or some other strategy according to your current Java EE Application Server.
After reading about the topic for another bunch of hours while playing around with every configuration property and/or annotation I could find a working solution for my usecase. It might not be the best or only solution, but since the question has received some bookmarks and upvotes, i'd like to share what i have so far:
At first, there was no way to get it working as expected when running the persistence-unit in managed mode. (<persistence-unit name="test" transaction-type="JTA"> - JTA is default if no value given.)
I decided to add another persistence-unit to the persistence xml, which is configured to run in unmanaged mode: <persistence-unit name="test2" transaction-type="RESOURCE_LOCAL">.
(Note: The waring about Multiple Persistence Units is just cause eclipse can't handle. It has no functional impact at all)
The unmanaged persitence-context requires local configuration of the database, since it is no longer container-provided:
<persistence-unit name="test2" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<class>test.AEntity</class>
<properties>
<property name="hibernate.connection.url" value="jdbc:mysql://localhost/test"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<property name="hibernate.connection.password" value="1234"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.archive.autodetection" value="true" />
<property name="hibernate.jdbc.batch_size" value="20" />
<property name="hibernate.connection.autocommit" value="false" />
</properties>
</persistence-unit>
A change required to the project would now be, that you add an unitName, whenever you use the #PersistenceContext annotation to retrieve a managed instance of the EntityManager.
But be aware, that you can only use #PersistenceContext for the managed persistence-unit. For the unmanaged one, you could implement a simple Producer and Inject the EntityManager using CDI whenever required:
#ApplicationScoped
public class Resources {
private static EntityManagerFactory emf;
static {
emf = Persistence.createEntityManagerFactory("test2");
}
#Produces
public static EntityManager createEm(){
return emf.createEntityManager();
}
}
Now, in the example given in the original Post, you need to Inject the EntityManager and manually take care about transactions.
#Stateful
public class TestService {
#Inject
private EntityManager em;
public void transactionalCreation() throws Exception {
em.getTransaction().begin();
try {
em.createNativeQuery(
"INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','a')")
.executeUpdate();
em.createNativeQuery(
"INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','b')")
.executeUpdate();
em.createNativeQuery(
"INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','c')")
.executeUpdate();
em.createNativeQuery(
"INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','d')")
.executeUpdate();
AEntity a = new AEntity();
a.setName("TestEntity1");
em.persist(a);
// force unique key violation, rollback should appear.
// em.createNativeQuery(
// "INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','d')")
// .executeUpdate();
em.getTransaction().commit();
} catch (Exception e) {
em.getTransaction().rollback();
}
}
}
My tests so far showed that mixing of native queries and persistence calls lead to the desired result: Either everything is commited or the transaction is rolledback as a whole.
For now, the solution seems to work. I will continue to validate it's functionality in the main project and check if there are any other sideeffects.
Another thing I need to verify is if it would be save to:
Inject both Versions of the EM into one Bean and mix usage. (First checks seem to work, even when using both ems at the same time on the same table(s))
Having both Versions of the EM operating on the same datasource. (Same data source would most likely be no problem, same tables I assume could lead to unexpected problems.)
ps.: This is Draft 1. I will continue to improve the answer and point out problems and/or drawbacks I'm going to find.
You have to add <hibernate.connection.release_mode key="hibernate.connection.release_mode" value="after_transaction" /> to your properties. After a restart should the Transaction handling work.
I created a "Java EE Web Module" project in IntelliJ. I then wrote a JaxRS annotated class that accepts JSON input. I then populate an annotated entity with the data, and try to persist it using a managed persistence context.
#Stateless
#Path("/states")
public class StateController {
#PersistenceContext(unitName = "testunit")
private EntityManager em;
#POST
#Path("/session_new")
#Produces({ MediaType.APPLICATION_JSON })
#Consumes({ MediaType.APPLICATION_JSON })
public Response session_new(final CustomerSessionRequest req) {
CustomerSessions cs = new CustomerSessions(req.session_data);
em.persist(cs);
em.flush();
System.out.println("New CustomerSession saved: " + cs.getCustomerSessionId());
return Response.ok(cs).build();
}
}
I have a data source configured within IntelliJ called "testdb", and a persistence unit named "testunit" that maps to that data source in the persistence tool window.
My persistence XML looks like this:
<persistence-unit name="testunit">
<jta-data-source>testdb</jta-data-source>
<class>datamodels.testdb.CustomerSessions</class>
<properties>
<property name="openjpa.ConnectionURL" value="jdbc:mysql://localhost:3306/testdb"/>
<property name="openjpa.ConnectionDriverName" value="com.mysql.jdbc.Driver"/>
<property name="openjpa.ConnectionUserName" value="testuser"/>
<property name="openjpa.ConnectionPassword" value="testpassword"/>
<property name="openjpa.Log" value="DefaultLevel=WARN, Runtime=INFO, Tool=INFO, SQL=TRACE"/>
<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema" />
</properties>
</persistence-unit>
</persistence>
Everything builds and deploys just fine, with no warnings. The request also runs just fine, and returns the expected response, with a new customer session ID generated.
However, nothing appears in the database.
So, my question: where is the data going, and how can I make the persist and flush calls work against my database?
EDIT:
I've tried several more things.
1) It looks like TomEE is using some kind of in-memory HSQL database with a data source name of "Default JDBC Data Source".
2) When I manually create the entity manager factory, and then the entity manager, everything works correctly:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("testunit");
EntityManager em = emf.createEntityManager();
try {
em.getTransaction().begin();
CustomerSessions cs = new CustomerSessions(req.session_data);
em.persist(cs);
em.flush();
em.getTransaction().commit();
System.out.println("New CustomerSession saved: " + cs.getCustomerSessionId());
return Response.ok(cs).build();
} catch (Exception ex) {
em.getTransaction().rollback();
return Response.serverError().entity("An exception occurred").build();
}
2) If I try to create the EntityManagerFactory using the #PersistenceUnit annotation, the same initial problem occurs.
There were two things I was missing:
I didn't specify <Resource> objects in tomee.xml (which I didn't
want to, since I wanted it to deploy with the app). I discovered
that this can be done in a resources.xml file in the WEB-INF or
META-INF directories.
I didn't have the MySQL driver jar in the
TomEE lib directory. Unfortunately TomEE was transparently loading
the Default JDBC driver instead, causing entities to persist in its
own magical database.
I still don't have an explanation for why application-managed (manually created) persistence contexts worked in the first place, but at least now I have it working consistently.
I am using a library where I need to get a data source and feed it into it. is there anyway I can get a connection from a connection pool? I am using Hibernate 4 with C3p0 connection pool.
here is my hibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/sampleDB</property>
<property name="connection.username">root</property>
<property name="connection.password">mypass</property>
<property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<property name="c3p0.max_size">100</property>
<property name="c3p0.min_size">1</property>
<property name="c3p0.idle_test_period">30</property>
<!-- SQL dialect -->
<property name="dialect">
org.hibernate.dialect.MySQLDialect
</property>
<!-- Shows Generated SQL Queries By Hibernate -->
<property name="show_sql">false</property>
<!-- Drop and re-create The Database Schema on Start up -->
<property name="hbm2ddl.auto">update</property>
<property name="cache.provider.class">org.hibernate.cache.NoCacheProvider</property>
</session-factory>
</hibernate-configuration>
If you are using hibernate and you want access to the c3p0 Connection pool it is already using , one easy approach would be to use the C3P0Registry class to find the DataSource, see here and here.
Probably you will find that getPooledDataSources() returns a Set containing single element, and that will be the DataSource hibernate has constructed. If you want to, you can also set the config param c3p0.dataSourceName (hibernate.c3p0.dataSourceName in hibernate config), and use C3P0Registry.pooledDataSourcesByName( dataSourceName ).
[If you will set your own name, it would probably be worth verifying that hibernate is not using the dataSourceName property itself. I don't think that it does, but I haven't checked. The easiest way to check would be to look at your logs for the pools config dump on init, and make sure that there's something like "dataSourceName -> z8kflt8uqkl8iymaxxkw|729f44" in it. If the name is a long random-ish String with a pipe in it, it's an instance-specific autogenerated identity token and you should feel free to set your own name. If you see a more sensible name, then hibernate has already set this property and may be expecting the name you see, so you should look up that name.]
Note that if you plan to use Connections from the DataSource directly, take care to ensure that all Connections get properly close()ed in finally blocks. If you "leak" Connections, i.e. if you check them out and fail to check them back in reliably, you'll eventually exhaust the pool and freeze your hibernate app.
Good luck!
Update: Example...
import java.util.Set;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.C3P0Registry;
// you probably want better Exception handling than this...
private DataSource findUniqueDataSource()
{
Set set = C3P0Registry.getPooledDataSources();
int sz = set.size();
if ( sz == 1 ) // yay, just one DataSource
return (DataSource) set.iterator().next();
else
throw new RuntimeException("No unique c3p0 DataSource, found:" + sz);
}
// be sure you have configured a dataSourceName in your c3p0 or hibernate config
private DataSource findDataSourceByName( String dataSourceName )
{ return C3P0Registry.pooledDataSourceByName(dataSourceName); }
And no, you should not "leak" Connections and expect the pool to clean up after you. You can forget to close Statements and ResultSets, and the pool will take care of them when you close() the Connection, but the pool doesn't know when it's safe to snatch a Connection back from a client that has failed to close it. Some applications hold Connections open for a long time (although that's bad practice, if you are using a Connection pool).
You can force c3p0 to clean up leaked Connections after a period of time, see the config parameter unreturnedConnectionTimeout. But this is an icky strategy; I recommend if you have a leak to use this in concert with debugUnreturnedConnectionStackTraces only temporarily, to understand where you are leaking Connections and then fix the problem.
c3p0 provides connection pool for Hibernate, as The built-in Hibernate connection pool is in no way intended for production use. It lacks several features found on any decent connection pool - According to Hibernate Community Documentation, for configuration of c3p0 with Hibernate you can refer to this or MKYong tutorial on Hibernate Community.
I got some trouble loading a list of objects from my database using Hibernate and lazy=true mode.
Hope that someone can help me out here.
I have a simple class here called UserAccount which looks like this:
public class UserAccount {
long id;
String username;
List<MailAccount> mailAccounts = new Vector<MailAccount>();
public UserAccount(){
super();
}
public long getId(){
return id;
}
public void setId(long id){
this.id = id;
}
public String getUsername(){
return username;
}
public void setUsername(String username){
this.username = username;
}
public List<MailAccount> getMailAccounts() {
if (mailAccounts == null) {
mailAccounts = new Vector<MailAccount>();
}
return mailAccounts;
}
public void setMailAccounts(List<MailAccount> mailAccounts) {
this.mailAccounts = mailAccounts;
}
}
I am mapping this class in Hibernate via the following mapping file:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="test.account.UserAccount" table="USERACCOUNT">
<id name="id" type="long" access="field">
<column name="USER_ACCOUNT_ID" />
<generator class="native" />
</id>
<property name="username" />
<bag name="mailAccounts" table="MAILACCOUNTS" lazy="true" inverse="true" cascade="all">
<key column="USER_ACCOUNT_ID"></key>
<one-to-many class="test.account.MailAccount" />
</bag>
</class>
</hibernate-mapping>
As you can see, lazy is set to "true" in the bag mapping element.
Saving the data to the database works fine:
Loading also works by calling loadUserAccount(String username) (see code below):
public class HibernateController implements DatabaseController {
private Session session = null;
private final SessionFactory sessionFactory = buildSessionFactory();
public HibernateController() {
super();
}
private SessionFactory buildSessionFactory() {
try {
return new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public UserAccount loadUserAccount(String username) throws FailedDatabaseOperationException {
UserAccount account = null;
Session session = null;
Transaction transaction = null;
try {
session = getSession();
transaction = session.beginTransaction();
Query query = session.createQuery("FROM UserAccount WHERE username = :uname").setParameter("uname", username));
account = (UserAccount) query.uniqueResult();
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw new FailedDatabaseOperationException(e);
} finally {
if (session.isOpen()) {
// session.close();
}
}
return account;
}
private Session getSession() {
if (session == null){
session = getSessionFactory().getCurrentSession();
}
return session;
}
}
The problem is just: When I access elements within the list "mailAccounts", I get the following exception:
org.hibernate.LazyInitializationException:
failed to lazily initialize a
collection of role:
test.account.UserAccount.mailAccounts,
no session or session was closed
I assume the reason for this exception is that the session got closed (don't know why and how) and thus Hibernate cannot load the list.
As you can see, I even removed the session.close() call from the loadUserAccount() method but the session still seems to be either get closed or replaced by another instance.
If I set lazy=false, then everything works smoothly but this is not what I wanted because I need the feature of loading data "on demand" due to performance issues.
So, if I can't be sure that my session is still valid after the method loadUserAccount(String username) terminated, what's the point of having that feature and how do I work around that?
Thanks for your help!
Ps: I am a Hibernate beginner so please excuse my noobishness.
Update: Here is my hibernate config.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.password">foo</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/mytable</property>
<property name="hibernate.connection.username">user</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
<!-- Auto create tables -->
<!-- <property name="hbm2ddl.auto">create</property>-->
<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class">thread</property>
<!-- Mappings -->
<mapping resource="test/account/SmampiAccount.hbm.xml"/>
<mapping resource="test/account/MailAccount.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Lazy Loading working or not has nothing to do with transaction boundaries. It only requires that the Session be open.
However, when the session is open depends on how you've actually set up the SessionFactory, which you did not tell us! There is config going on behind what SessionFactory.getCurrentSession() actually does! If you're letting it go with the default version of ThreadLocalSessionContext and not doing anything to manage the life cycle, it does indeed actually default to closing the session when you commit. (Hence the common conception that broadening transaction boundaries is the 'fix' for a lazy load exception.)
If you manage you own session life cycle with sessionFactory.openSession() and session.close() you will be able to lazy load fine within the session life cycle, outside transaction boundaries. Alternately you can provide a subclass of ThreadLocalSessionContext that manages the session life-cycle with the boundaries you desire. There are also readily available alternatives such as the OpenSessionInView filter that can be used in web applications to bind the session life-cycle to the web request life cycle.
edit: You can also of course just initialize the list inside the transaction if that works for you. I just think that leads to really clunky APIs when you need either a new method signature of some kind of 'flag' parameter for each level of hydration of your entity. dao.getUser() dao.getUserWithMailAccounts() dao.getUserWIthMailAccountsAndHistoricalIds() and so on.
edit 2: You may find this helpful for different approaches to how long the session stays open/the relationship between session scope and transaction scope. (particularly the idea of session-per-request-with-detached-objects vs session-per-conversation.)
http://community.jboss.org/wiki/SessionsAndTransactions
It depends on your requirements and architecture just how big a conversation actually is.
The reason you're getting the exception might be that the transaction you load the data in is closed (and the session with it), i.e. you're working outside the session. Lazy loading is especially useful when working with entities in one session (or across sessions when correctly employing a second level cache).
AFAIK you can tell Hibernate to automatically open a new session for lazy loading but I didn't use that for a while and thus I'd have to look up how that works again.
You need to wrap your entire process within a transaction.
So instead of starting and commiting the transaction in loadUserAccount, do it outside of that.
For example:
public void processAccount()
{
getSession().beginTransaction();
UserAccount userAccount = loadUserAccount("User");
Vector accts = userAccount.getMailAccounts(); //This here is lazy-loaded -- DB requests will happen here
getSession().getTransaction().commit();
}
Usually, you want to wrap your transaction around the entire unit of work. I suspect that your understanding of transactions is a little too fine grained.