I created a small SpringBoot App which is used together with Hibernate to work with our Oracle Databse.
But I ran into the following Problem:
Whenever I load an Object from the database with SessionFactory.createQuery() and then query.getResultList(). I do get a List of Results with the correct Class annotations (when looking at the code in debug mode). But I cannot do MyClass x = list.get(0), even though the list is a List of MyClass. I can only get an Object but cannot "cast" to the correct class.
Uidnr is a simple Class with no join tables or any other dependencies on other tables form the database. Its only BigDecimal's, String's and Timestamp's.
Here is the code and configs of everything:
POST Endpoint:
#RequestMapping(value = "/e10", method = RequestMethod.POST, produces = MediaType.APPLICATION_XML_VALUE)
#ResponseBody
public String e10() {
Database db = new Database();
List<Uidnr> bel = db.getUidnrList(new BigDecimal("1009316"));
Uidnr element = bel.get(0); //CLASSCASTEXCEPTION HERE!!!
return element.getNr();
}
Method to get the Class from Database:
public List<Uidnr> getUidnrList(BigDecimal id) {
SessionFactory factory = null;
List<Uidnr> uidnr = new ArrayList<Uidnr>();
Session session = null;
try {
factory = getSessionFactory();
session = factory.openSession();
Query<Uidnr> query = session.createQuery("from Uidnr where adrid=:sblid", Uidnr.class);
query.setParameter("sblid", id);
uidnr = query.getResultList(); //This is a List<Uidnr
} catch (HibernateException ex) {
logger.error("Error loading Sendungen.", ex);
} finally {
close(factory, session);
}
return uidnr;
}
SessionFactory:
private SessionFactory getSessionFactory() {
SessionFactory sf = null;
File optextconf = new File("conf/hibernate.cfg.xml");
Configuration c = new Configuration();
if (optextconf.exists()) {
c.configure(optextconf);
c.addResource("eu/lbase/invsvc/app/model/internal/Uidnr.hbm.xml");
sf = c.buildSessionFactory();
logger.info("Session factory loaded from external file {}.", optextconf.getAbsolutePath());
} else {
logger.error("Configuration {} not found. Can not connect to database.", optextconf.getAbsolutePath());
}
return sf;
}
Uidnr.hbm.xml file located under src/main/resources, in a package called eu.lbase.invsvc.app.model.internal:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="eu.lbase.invsvc.app.model.internal.Uidnr" table="SUID_UIDNR">
<composite-id>
<key-property name="adrid" column="UID_ADRID" />
<key-property name="staid" column="UID_STAID" />
</composite-id>
<property name="nr" column="UID_NR"/>
<property name="stnr" column="UID_STNR"/>
<property name="deflt" column="UID_DEFLT"/>
<property name="aend" column="UID_AEND"/>
<property name="usrid" column="UID_USRID"/>
<property name="stktonr" column="UID_STKTONR"/>
<property name="zoktonr" column="UID_ZOKTONR"/>
<property name="vollmacht" column="UID_VOLLMACHT"/>
</class>
</hibernate-mapping>
Exception:
java.lang.ClassCastException: eu.lbase.invsvc.app.model.internal.Uidnr cannot be cast to eu.lbase.invsvc.app.model.internal.Uidnr
at eu.lbase.invsvc.app.controller.WebController.e10(WebController.java:91) ~[main/:na]
Some Tests:
System.out.println(bel.getClass()); //class java.util.ArrayList
Object test = bel.get(0);
System.out.println(test.getClass()); //class eu.lbase.invsvc.app.model.internal.Uidnr
System.out.println(bel.get(0) instanceof eu.lbase.invsvc.app.model.internal.Uidnr); //false
Related
Getting this exception while using hibernate template in spring.
Caused by: org.hibernate.QueryException: Not all named parameters have been set: [roledesc, sapid, pass] [
select u from Role as r left join r.users as u where u.sapid=:sapid and u.pass=:pass and r.roledesc=:roledesc
]
at org.hibernate.impl.AbstractQueryImpl.verifyParameters(AbstractQueryImpl.java:291)
at org.hibernate.impl.AbstractQueryImpl.verifyParameters(AbstractQueryImpl.java:275)
at org.hibernate.impl.QueryImpl.list(QueryImpl.java:75)
at org.springframework.orm.hibernate3.HibernateTemplate$33.doInHibernate(HibernateTemplate.java:988)
at org.springframework.orm.hibernate3.HibernateTemplate$33.doInHibernate(HibernateTemplate.java:1)
at org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:406)
... 30 more
Here is the class where I'm trying to fetch the named query:
public class LoginDaoImpl extends AbstractDaoImpl implements LoginDao {
private static final Logger LOGGER = Logger.getLogger(LoginDaoImpl.class);
#Override
public User loginCheck(User user, Role role) {
LOGGER.debug("Inside validate user:" + user.getPass());
user.setName("");
List<User> employee = new ArrayList<User>();
Query query = (Query) template.findByNamedQuery("findRoleforaUser");
query.setString("sapid", user.getSapid());
query.setString("pass", user.getPass());
query.setString("roledesc", role.getRoledesc());
employee = query.list();
if(employee == null)
{
user.setName("");
}
else if(employee.isEmpty())
{
user.setName("");
}
else if (!(user.getPass().equals(employee.get(0).getPass())))
LOGGER.info("No match found!");
else {
user.setName( employee.get(0).getName());
LOGGER.debug("\nUser \"" + employee.get(0).getName()
+ "\" found and login is successful ");
}
return user;
}
}
And here is my .hbm file contents where the named query is defined:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping SYSTEM "D:\My HCL JEE Progs\CBA_Quiz\src\dtd\hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.hcl.cba.payments.domain.Role" table="roles">
<id name="roleid" type="int" column="ROLEID" />
<property name="roledesc" type="string" column="ROLEDESC" />
<set name="users" cascade="all">
<key column="roleid" />
<one-to-many class="com.hcl.cba.payments.domain.User" />
</set>
</class>
<query name="findRoleforaUser">
<![CDATA[select u from Role as r left join r.users as u where u.sapid=:sapid and u.pass=:pass and r.roledesc=:roledesc]]>
</query>
</hibernate-mapping>
P.S:I tried running/accessing the query without hibernate template and it ran fine and I had set the variables the same way too:
Query query = session.findByNamedQuery("findRoleforaUser");
query.setString("sapid", user.getSapid());
query.setString("pass", user.getPass());
query.setString("roledesc", role.getRoledesc());
employee =query.list();
This should work
List<User> employees = new ArrayList<User>();
String[] paramNames = { "sapid", "pass", "roledesc" };
Object[] values = { user.getSapid(), user.getPass(), role.getRoledesc() };
employees = template.findByNamedQueryAndNamedParam("findRoleforaUser", paramNames, values);
I'm trying to run a simple MyBatis example, selecting all rows from the "trains" table.
The problem is that the query performs, but it returns a list with the correct number of elements, but populated with null values.
The same query runned directly with JDBC PreparedStatement works fine.
Perhaps it's a configuration problem, but I cannot figure out what I'm doing wrong.
Here is the code. Thanks in advance.
Train.java
package org.example.mybatis.domain;
public class Train implements Serializable
{
private int id;
private String type;
// getters and setters
}
TrainMapper.java
package org.example.mybatis.persistence;
public interface TrainMapper {
List<Train> getAllTrains();
}
TrainSelector.java
package org.example.mybatis.test;
public class TrainSelector implements TrainMapper {
private static String resource = "mybatis-config.xml";
private static SqlSessionFactory factory = null;
private SqlSessionFactory getSqlSessionFactory()
{
if (factory == null)
{
try {
InputStream inputStream = Resources.getResourceAsStream(resource);
factory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
return factory;
}
#Override
public List<Train> getAllTrains()
{
List<Train> trains = null;
SqlSession session = getSqlSessionFactory().openSession();
try {
TrainMapper mapper = session.getMapper(TrainMapper.class);
trains = mapper.getAllTrains();
} finally {
session.close();
}
return trains;
}
public static void main(String[] args) {
List<Train> trains = null;
TrainSelector trainSelector = new TrainSelector();
trains = trainSelector.getAllTrains();
System.out.println(trains);
}
}
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="database.properties" />
<typeAliases>
<typeAlias alias="Train" type="org.example.mybatis.domain.Train" />
<!--package name="org.example.mybatis.domain" />-->
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${database.driver}" />
<property name="url" value="${database.url}" />
<property name="username" value="${database.username}" />
<property name="password" value="${database.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/example/mybatis/persistence/TrainMapper.xml" />
</mappers>
</configuration>
TrainMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mybatis.persistence.TrainMapper">
<cache />
<select id="getAllTrains" parameterType="list" resultType="Train">
SELECT *
FROM trains
</select>
</mapper>
JdbcStatementExample.java
package org.example.mybatis.test;
public class JdbcStatementExample {
private static void selectAllTrains() throws SQLException
{
String sql = "SELECT * FROM trains";
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
String url = "jdbc:mysql://localhost/testing";
String user = "test";
String password = "test";
try {
conn = DriverManager.getConnection(url, user, password);
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
while (rs.next()) {
String id = rs.getString("train_id");
String type = rs.getString("train_type");
System.out.println("id: " + id);
System.out.println("type: " + type);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (ps != null) {
ps.close();
}
if (conn != null) {
conn.close();
}
}
}
public static void main(String[] args)
{
try {
selectAllTrains();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
The names of the columns in the result set are different from the names of the properties in the Train object. You need an explicit result map to let Mybatis know which column is to be mapped to which property.
<resultMap id="trainMap" type="Train>
<id property="id" column="train_id" javaType="java.lang.Integer" jdbcType="INTEGER"/>
<result property="type" column="train_type" javaType="java.lang.String" jdbcType="VARCHAR"/>
</resultMap>
Making your select element into
<select id="getAllTrains" parameterType="list" resultType="trainMap">
SELECT * FROM trains
</select>
Other option is to use column names an aliases.
The column names will be your database's and the aliases will be set to match with your Train object properties:
<select id="getAllTrains" parameterType="list" resultType="trainMap">
SELECT
train_id as id,
train_type as type
FROM trains
</select>
I had the same problem, but only for fields with multiple words. Of course my naming convention in SQL was user_id and in java was userId. This piece of config inside my mybatis-config.xml file saved the day:
<settings>
<setting name="mapUnderscoreToCamelCase" value="false"/>
</settings>
or for properties file:
mybatis.configuration.map-underscore-to-camel-case=true
credit: https://chois9105.github.io/spring/2017/12/31/configuring-mybatis-underscore-to-camel-case.html
Results can be mapped as described by Seeta or in the official docs here:
https://mybatis.org/mybatis-3/sqlmap-xml.html
In MyBatis 3.x the example doesn't work as you need to set resultMap rather than resultType. And you must not set both at the same time! Working example looks like:
<select id="getAllTrains" parameterType="list" resultMap="trainMap">
SELECT * FROM trains
</select>
if you are using spring boot, you can change the map-underscore-to-camel-case property as true like below. because most if the time we use _ (user_id) when create the table attributes. but in java we use camelCase (userId) for the variables. then mybatis don't know about that and when it tries to mapping, the error is thrown.
mybatis.configuration.map-underscore-to-camel-case=true
I want to store XML data in an Oracle XMLType column with registered XML schema file. Both XML files and the XSD schema are valid and XMLs conform to the schema. To get Hibernate work with XMLType, I used the Hibernate mapping Document-XMLType which can be found here:
http://solecjj.blogspot.com/2011/02/hibernate-with-oracle-xmltype.html
My Hibernate mapping XML looks like this:
...
<hibernate-mapping>
<class name="cz.zcu.kiv.eegdatabase.data.pojo.ScenarioType1" schema="JPERGLER" table="SCENARIO_TABLE_1">
<id name="scenarioId" type="int">
<column name="SCENARIO_ID" precision="22" scale="0"/>
<generator class="increment"/>
</id>
<property name="scenarioXml" type="cz.zcu.kiv.eegdatabase.data.datatypes.OracleXMLType">
<column name="SCENARIO_XML"/>
</property>
</class>
</hibernate-mapping>
And this is the corresponding POJO class:
public ScenarioType1() {
}
public ScenarioType1(int scenarioId, Document scenarioXml) {
this.scenarioId = scenarioId;
this.scenarioXml = scenarioXml;
}
public int getScenarioId() {
return scenarioId;
}
private void setScenarioId(int scenarioId) {
this.scenarioId = scenarioId;
}
public Document getScenarioXml() {
return scenarioXml;
}
public void setScenarioXml(Document scenarioXml) {
this.scenarioXml = scenarioXml;
}
The document object is created in a controller class and is handed over the DAO object as an attribute of its POJO object:
protected ModelAndView onSubmit(HttpServletRequest request,
HttpServletResponse response, Object command, BindException bindException)
throws Exception {
MultipartFile xmlFile = data.getDataFileXml();
ScenarioType1 scenarioType1;
scenarioType1 = new ScenarioType1();
...
if ((xmlFile != null) && (!xmlFile.isEmpty())) {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
InputStream inputStream = xmlFile.getInputStream();
Document doc = docBuilder.parse(inputStream);
scenarioType1.setScenarioXml(doc);
inputStream.close();
}
scenarioTypeDao.create(scenarioType1);
...
}
The DAO class and interface are very simple:
public interface ScenarioTypeDao extends GenericDao<ScenarioType1, Integer> {
}
public class SimpleScenarioTypeDao extends SimpleGenericDao<ScenarioType1, Integer>
implements ScenarioTypeDao {
public SimpleScenarioTypeDao() {
super(ScenarioType1.class);
}
}
When the method onSubmit() in the controller class is processed, I get the following error message:
Hibernate: insert into JPERGLER.SCENARIO_TABLE_1 (SCENARIO_XML, SCENARIO_ID) values(?,?)
SEVERE: Servlet.service() for servlet dispatcher threw exception
org.springframework.dao.CleanupFailureDataAccessException:
Failed to flush session before close: Could not execute JDBC batch update;
nested exception is org.hibernate.exception.GenericJDBCException:
Could not execute JDBC batch update
...
Caused by:
org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update
at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:126)
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:114)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
...
Caused by:
java.sql.BatchUpdateException:
ORA-31011: XML parsing failed
at oracle.jdbc.driver.DatabaseError.throwBatchUpdateException(DatabaseError.java:566)
at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:9365)
at oracle.jdbc.driver.OracleStatementWrapper.executeBatch (OracleStatementWrapper.java:210)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
It looks that the created Document object is filled with correct data, so the error seems to happen on the Hibernate mapping side. I'm starting to feel desperate, any help would be appreciated.
This is the sample XML file I'm trying to insert:
<?xml version="1.0"?>
<scenarios>
<scenario name="P300" src="p300.xml"/>
<scenario src="070608_p300.xml" name="070608_p300" />
<scenario src="cisla_070608.xml" name="cisla_070608" />
</scenarios>
I'm trying to create a multiaction web controller using Spring annotations. This controller will be responsible for adding and removing user profiles and preparing reference data for the jsp page.
#Controller
public class ManageProfilesController {
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(UserAccount.class,"account", new UserAccountPropertyEditor(userManager));
binder.registerCustomEditor(Profile.class, "profile", new ProfilePropertyEditor(profileManager));
logger.info("Editors registered");
}
#RequestMapping("remove")
public void up( #RequestParam("account") UserAccount account,
#RequestParam("profile") Profile profile) {
...
}
#RequestMapping("")
public ModelAndView defaultView(#RequestParam("account") UserAccount account) {
logger.info("Default view handling");
ModelAndView mav = new ModelAndView();
logger.info(account.getLogin());
mav.addObject("account", account);
mav.addObject("profiles", profileManager.getProfiles());
mav.setViewName(view);
return mav;
}
...
}
Here is the part of my webContext.xml file:
<context:component-scan base-package="ru.mirea.rea.webapp.controllers" />
<context:annotation-config/>
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>
...
/home/users/manageProfiles=users.manageProfilesController
</value>
</property>
</bean>
<bean id="users.manageProfilesController" class="ru.mirea.rea.webapp.controllers.users.ManageProfilesController">
<property name="view" value="home\users\manageProfiles"/>
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
However, when i open the mapped url, i get exception:
java.lang.IllegalArgumentException: Cannot convert value of type [java.lang.String] to required type [ru.mirea.rea.model.UserAccount]: no matching editors or conversion strategy found
I use spring 2.5.6 and plan to move to the Spring 3.0 in some not very distant future. However, according to this JIRA https://jira.springsource.org/browse/SPR-4182 it should be possible already in spring 2.5.1.
The debug shows that the InitBinder method is correctly called.
What am i doing wrong?
Update:
public class UserAccountPropertyEditor extends PropertyEditorSupport {
static Logger logger = Logger.getLogger(UserAccountPropertyEditor.class);
public UserAccountPropertyEditor(IUserDAO dbUserManager) {
this.dbUserManager = dbUserManager;
}
private IUserDAO dbUserManager;
public String getAsText() {
UserAccount obj = (UserAccount) getValue();
if (null==obj) {
return "";
} else {
return obj.getId().toString();
}
}
public void setAsText(final String value) {
try {
Long id = Long.parseLong(value);
UserAccount acct = dbUserManager.getUserAccountById(id);
if (null!=acct) {
super.setValue(acct);
} else {
logger.error("Binding error. Cannot find userAccount with id ["+value+"]");
throw new IllegalArgumentException("Binding error. Cannot find userAccount with id ["+value+"]");
}
} catch (NumberFormatException e) {
logger.error("Binding error. Invalid id: " + value);
throw new IllegalArgumentException("Binding error. Invalid id: " + value);
}
}
}
There are no errors logged from UserAccountPropertyEditor.
I don't think you want to be specifying the field argument to WebDataBinder.registerCustomEditor(). This intended to work alongside form-backing objects, and you're not using that.
Try the simpler 2-arg method instead, and it should work:
binder.registerCustomEditor(UserAccount.class, new UserAccountPropertyEditor(userManager));
binder.registerCustomEditor(Profile.class, new ProfilePropertyEditor(profileManager));
How can I make this work in unit tests using Hibernate 3.3.1ga and HSQLDB:
#Entity
#Table(name="CATEGORY", schema="TEST")
public static class Category { ... }
The problem is that Hibernate expects the schema to exist. The second problem is that Hibernate issues the CREATE TABLE TEST.CATEGORY before any of my code runs (this happens deep inside Spring's test setup), so I can't get a connection to the DB before Hibernate and create the schema manually.
But I need the schema because I have to access different databases in the real code. What should I do?
Hibernate 3.3.1ga, HSQLDB, Spring 2.5
You could write a class that implements InitializingBean:
public class SchemaCreator implements InitializingBean {
private String schema;
private DataSource dataSource;
public String getSchema() {
return schema;
}
public void setSchema(String schema) {
this.schema = schema;
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
#Override
public void afterPropertiesSet() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.execute("CREATE SCHEMA " + schema + " AUTHORIZATION DBA");
}
}
You then have to define a bean in your bean definition file of this class (I'm taking a shot in the dark as to what your existing bean definitions look like).
<bean id="dataSource" class="...">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:mem:test"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="sessionFactory" depends-on="schemaCreator" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
...
</bean>
<bean id="schemaCreator" class="SchemaCreator">
<property name="dataSource" ref="dataSource"/>
<property name="schema" value="TEST"/>
</bean>
By using the depends-on attribute of Hibernate's bean, Spring will ensure that the schemaCreator bean will be initialized first, causing the schema to exist just in time. This should also make your intentions clearer.
My current solution looks like this:
#Override
protected String[] getConfigLocations() {
createHSQLDBSchemas ();
return new String[]{
"test-spring-config.xml"
};
}
private static boolean hsqldbSchemasCreated = false;
public static void createHSQLDBSchemas ()
{
if (hsqldbSchemasCreated)
return;
try
{
log.info ("createHSQLDBSchemas");
Class.forName("org.hsqldb.jdbcDriver").newInstance();
Connection c = DriverManager.getConnection("jdbc:hsqldb:mem:test", "sa", "");
Statement stmt = c.createStatement ();
String sql;
sql = "CREATE SCHEMA xxx AUTHORIZATION DBA";
log.info (sql);
stmt.execute (sql);
stmt.close ();
c.close ();
}
catch (Exception e)
{
throw new ShouldNotHappenException (e);
}
hsqldbSchemasCreated = true;
}
but that feels like a really ugly hack. Isn't there a better solution?
Below is an example of how you can create spring config with test hslqdb
It automaticly detects all your schemas from #Table(schema =...) and creates them for you.
If it is just for testing this should work for you:
import org.reflections.Reflections; //maven artifact: 'org.reflections:reflections:0.9.9-RC1'
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import javax.persistence.Table;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
#Configuration
#ComponentScan("com.test.collection")
public class CollectionConfig {
private static final String[] ENTITY_PACKAGES = { "com.test.collection.domain.dao" };
private static final String CONFIGURATION_LOCATION = "/movie-collection-hibernate.cfg.xml";
#Bean( name = "testSessionFactory" )
#Lazy
public LocalSessionFactoryBean getTestSessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setPackagesToScan( ENTITY_PACKAGES );
Properties hibernateProperties = getHibernateHsqlTestDbProperties();
sessionFactory.setHibernateProperties( hibernateProperties );
createNonStandardSchemas( hibernateProperties );
return sessionFactory;
}
private void createNonStandardSchemas( Properties properties ) {
final String DEFAULT_SCHEMA = "";
Set<String> schemas = new HashSet<>();
Reflections reflections = new Reflections( ENTITY_PACKAGES );
Set<Class<?>> annotatedClasses =
reflections.getTypesAnnotatedWith( Table.class );
for ( Class<?> clazz : annotatedClasses ) {
Table table = clazz.getAnnotation( Table.class );
if ( !DEFAULT_SCHEMA.equals( table.schema() ) ) {
schemas.add( table.schema() );
}
}
if ( !schemas.isEmpty() ) {
DriverManagerDataSource driverManager = new DriverManagerDataSource();
driverManager.setDriverClassName( properties.getProperty( "hibernate.connection.driver_class" ) );
driverManager.setUrl( properties.getProperty( "hibernate.connection.url" ) );
driverManager.setUsername( properties.getProperty( "hibernate.connection.username" ) );
driverManager.setPassword( properties.getProperty( "hibernate.connection.password" ) );
JdbcTemplate jdbcTemplate = new JdbcTemplate( driverManager );
for ( String schemaName : schemas ) {
jdbcTemplate.execute(
String.format( "DROP SCHEMA IF EXISTS %s", schemaName)
);
jdbcTemplate.execute(
String.format( "CREATE SCHEMA %s AUTHORIZATION DBA", schemaName)
);
}
}
}
private Properties getHibernateHsqlTestDbProperties() {
Properties prop = new Properties();
prop.setProperty( "hibernate.connection.driver_class", "org.hsqldb.jdbcDriver" );
prop.setProperty( "hibernate.connection.url", "jdbc:hsqldb:mem:test" );
prop.setProperty( "hibernate.connection.username", "sa" );
prop.setProperty( "hibernate.connection.password", "test" );
prop.setProperty( "hibernate.connection.pool_size", "5" );
prop.setProperty( "hibernate.dialect", "org.hibernate.dialect.HSQLDialect" );
prop.setProperty( "hibernate.current_session_context_class", "thread" );
prop.setProperty( "hibernate.cache.provider_class", "org.hibernate.cache.internal.NoCachingRegionFactory" );
prop.setProperty( "hibernate.show_sql", "false" );
prop.setProperty( "hibernate.format_sql", "false" );
prop.setProperty( "hibernate.use_sql_comments", "false" );
prop.setProperty( "hibernate.hbm2ddl.auto", "create-drop" );
return prop;
}
}
And here is a test sample:
#ContextConfiguration( classes = CollectionConfig.class )
#DirtiesContext( classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD )
public class DaoMappingTest extends AbstractTestNGSpringContextTests {
#Autowired
private SessionFactory testSessionFactory;
#Test
public void thatMovieIsSaved() {
Movie killBill = getKillBillMovie0();
saveToDb( Arrays.asList(killBill) );
Session querySession = testSessionFactory.openSession();
List<Movie> movies = querySession.createQuery( "from Movie" ).list();
querySession.close();
assertThat( movies ).containsExactly( killBill );
}
#Test
public void that2MoviesIsSaved() {
Movie killBill = getKillBillMovie0();
Movie terminator = getTerminatorMovie1();
saveToDb( Arrays.asList( killBill, terminator ) );
Session querySession = testSessionFactory.openSession();
List<Movie> movies = querySession.createQuery( "from Movie" ).list();
querySession.close();
assertThat( movies ).containsOnly( killBill, terminator );
}
private void saveToDb( List<?> objects ) {
Session session = testSessionFactory.openSession();
session.beginTransaction();
for( Object obj : objects) {
session.save( obj );
}
session.getTransaction().commit();
session.close();
}
#AfterSuite
public void tearDown() {
testSessionFactory.close();
}
}
It looks to me like you have a reproducible bug in the Hibernate DDL creation code. You should report a bug - it's a long term solution but it's the way things are done in open source. Of course you might want to produce a patch, but I never found the Hibernate code base easy to hack.
I ran into the same problem where MS SQL Server wants the catalog and schema defined, but HSQLDB does not. My solution was to load a custom orm.xml file (via persistence.xml) specifically for MS SQL Server that sets the catalog and schema.
1.Only specify the #Table name (omit any catalog or schema info) for your entity:
#Entity
#Table(name="CATEGORY")
public static class Category { ... }
2.Specify two persistence-unit nodes in your META-INF/persistence.xml file
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<!--
| For production and integration testing we use MS SQL Server, which needs
| the catalog and schema set (see orm-mssql.xml).
|-->
<persistence-unit name="com.mycompany.prod">
<mapping-file>META-INF/orm-mssql.xml</mapping-file>
</persistence-unit>
<!--
| For unit testing we use HSQLDB, which does not need the catalog or schema.
|-->
<persistence-unit name="com.mycompany.test" />
</persistence>
3.Specify the default catalog and schema in the orm-mssql.xml file:
<entity-mappings version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd">
<persistence-unit-metadata>
<!--
| Set the catalog and schema for MS SQL Server
|-->
<persistence-unit-defaults>
<schema>MYSCHEMA</schema>
<catalog>MYCATALOG</catalog>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings>
4.I'm using Spring to configure JPA, so I use a property-placeholder for the value of the persistenceUnitName:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
<property name="persistenceUnitName" value="${entityManagerFactory.persistenceUnitName}" />
</bean>
For unit tests, use 'com.mycompany.test' and for integration-tests/production deployments, use 'com.mycompany.prod'.