MyBatis in maven multiproject environment

777 views
Skip to first unread message

AntPort

unread,
Oct 15, 2010, 1:36:39 PM10/15/10
to mybatis-user
We have a maven multiproject configuration that looks like this:

Parent project consists of four projects:

1.Core project

2. AppFoundations parent consists of:
--- domain
--- dao
--- service
--- facade

3. Finances parent consists of:
--- domain
--- dao
--- service
--- facade

4. Web1 project (dependency on core + AppFoundations)

5. Web2 project (dependency on core + AppFoundations + Finances)

We keep our sql maps in dao layer of every project.

My question is: Can we have more than one xml file with the definition
of mapper resources?
Reason for this is the fact that Web1 and Web2 have different
dependencies, and Web1 does not need sql maps from Finances project.
We wanted to have more usability, so we share certan projects between
other projects.
If we put paths to all our resource files in one config file,
deployment of Web1 will crash because map files from Finances cannot
be found (project does not have dependency to Finances)...

We use MyBatis 3 with Spring...

Thanks,
Antonio

Larry Meadors

unread,
Oct 15, 2010, 1:43:52 PM10/15/10
to mybati...@googlegroups.com
You could do the code-based configuration instead of xml-based, you
could use ResolverUtil to add the mappers dynamically.

Easy peasy.

Larry

Eduardo

unread,
Oct 15, 2010, 3:07:25 PM10/15/10
to mybatis-user
I think I would have one appContext.xml in each one of your modules.
Web modules will include those appContext.xml that they need in their
own appContext.xml plus their own beans.
SqlSessionFactoryBean could be defined in your web modules appContext
so you will have just one in your app. And it will be reused in your
core jars.
You can also have a commont appContext.xml that searches for the rest
using wildcards. If any .xml file is not in the classpath it wont
fail.
You can also have just on xml file that searches for all beans with
component-scan and for mappers using the MapperScanner (similar to
what Larry has said). The MapperScanner is just on svn, not in RC1.

Hunter

unread,
Oct 15, 2010, 9:29:54 PM10/15/10
to mybatis-user
I would define SqlSessionFactoryBean in the core project then each
subproject would define its own MapperScanner / MapperFactoryBeans.

See this article for how to configure a parent Spring context that you
can share with other modules:
http://blog.springsource.com/2007/06/11/using-a-shared-parent-application-context-in-a-multi-war-spring-application/

Eduardo

unread,
Oct 16, 2010, 5:33:02 AM10/16/10
to mybatis-user

Using the parentContextKey is useful if you are building a multi-war
package, I mean, an ear with more than one war. If you do that you
will face classloading visibility problems and the parentContextKey
trick is a good option.

If you are buiding independent wars, with their dependencies in their
web-inf/lib dir you can simply link your appContexts with include
tags.

And yes, as Hunter says, SqlSessionFactoryBean and datasource would go
better in the core package.

On 16 oct, 03:29, Hunter <hpresn...@gmail.com> wrote:
> I would define SqlSessionFactoryBean in the core project then each
> subproject would define its own MapperScanner / MapperFactoryBeans.
>
> See this article for how to configure a parent Spring context that you
> can share with other modules:http://blog.springsource.com/2007/06/11/using-a-shared-parent-applica...

AntPort

unread,
Oct 19, 2010, 2:34:21 AM10/19/10
to mybatis-user
Thanks guys, that was quite helpful!

Antonio

AntPort

unread,
Oct 20, 2010, 10:27:25 AM10/20/10
to mybatis-user
Still, my problems are not completely solved :(
I have another one...
My SqlSessionFactoryBean configuration looks like this (I want to load
all the mappers from all the projects in the classpath):

<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="mapperLocations" value="classpath*:config/
sqlmapconfigs/*.xml" />
<property name="dataSource" ref="dataSource" />
</bean>

When I turn the debugging on I get:

DEBUG 10-20 15:57:24 Parsed mapper file: 'URL [jar:file:/home/valik/
BlazeDS/tomcat/webapps/dev-app/WEB-INF/lib/java-basics-dao-0.0.1-
SNAPSHOT.jar!/config/sqlmapconfigs/
ABCNEW_A_CURRS_SqlMap.xml]' (JakartaCommonsLoggingImpl.java:27)

DEBUG 10-20 15:57:24 Parsed mapper file: 'URL [jar:file:/home/valik/
BlazeDS/tomcat/webapps/dev-app/WEB-INF/lib/java-basics-dao-0.0.1-
SNAPSHOT.jar!/config/sqlmapconfigs/
STORED_PROCEDURE_COMMITER_SqlMap.xml]' (JakartaCommonsLoggingImpl.java:
27)

DEBUG 10-20 15:57:24 Parsed mapper file: 'URL [jar:file:/home/valik/
BlazeDS/tomcat/webapps/dev-app/WEB-INF/lib/java-basics-dao-0.0.1-
SNAPSHOT.jar!/config/sqlmapconfigs/
ABCNEW_A_USR_SET_SqlMap.xml]' (JakartaCommonsLoggingImpl.java:27)

DEBUG 10-20 15:57:24 Parsed mapper file: 'URL [jar:file:/home/valik/
BlazeDS/tomcat/webapps/dev-app/WEB-INF/lib/java-basics-dao-0.0.1-
SNAPSHOT.jar!/config/sqlmapconfigs/
ABCNEW_A_OB_SET_SqlMap.xml]' (JakartaCommonsLoggingImpl.java:27)

(--cropped--)

DEBUG 10-20 15:57:24 Parsed mapper file: 'URL [jar:file:/home/valik/
BlazeDS/tomcat/webapps/dev-app/WEB-INF/lib/java-erp-finance-dao-0.0.1-
SNAPSHOT.jar!/config/sqlmapconfigs/
ABCNEW_B_TEM_STA_SqlMap.xml]' (JakartaCommonsLoggingImpl.java:27)

DEBUG 10-20 15:57:24 Parsed mapper file: 'URL [jar:file:/home/valik/
BlazeDS/tomcat/webapps/dev-app/WEB-INF/lib/java-erp-finance-dao-0.0.1-
SNAPSHOT.jar!/config/sqlmapconfigs/
ABCNEW_B_DOK_KNJ_SqlMap.xml]' (JakartaCommonsLoggingImpl.java:27)

DEBUG 10-20 15:57:24 Parsed mapper file: 'URL [jar:file:/home/valik/
BlazeDS/tomcat/webapps/dev-app/WEB-INF/lib/java-erp-finance-dao-0.0.1-
SNAPSHOT.jar!/config/sqlmapconfigs/
ABCNEW_B_TEM_SqlMap.xml]' (JakartaCommonsLoggingImpl.java:27)


(--cropped--)

...and this seems to be OK, but when I try to execute query from one
of the aforementioned mappers, I get this error:

### Error querying database. Cause:
java.lang.IllegalArgumentException: Mapped Statements collection does
not contain value for ABCNEW_A_OB_SET_SqlMap.selectObSetByPrimaryKey
### Cause: java.lang.IllegalArgumentException: Mapped Statements
collection does not contain value for
ABCNEW_A_OB_SET_SqlMap.selectObSetByPrimaryKey


Changing the sqlSessionFactory bean property value mapperLocation from
"classpath*:..." to "classpath:...", things initially seem to be
working, but only the mappers from the first project are loaded, for
all others I get the same error...

Antonio

Eduardo

unread,
Oct 20, 2010, 1:11:13 PM10/20/10
to mybatis-user
Just in case. You should call your methods using your namespaces.
Could you post how you call my batis and one of your xml files (i.e.
ABCNEW_A_USR_SET_SqlMap.xml) ??

Hunter

unread,
Oct 20, 2010, 6:57:40 PM10/20/10
to mybatis-user
To add to Eduardo's reply - What is the value of the namespace
attribute in ABCNEW_A_OB_SET_SqlMap.xml? The value for the namespace
should be a fully qualified class name.

It looks like you are trying to call
ABCNEW_A_OB_SET_SqlMap.selectObSetByPrimaryKey(). Is
ABCNEW_A_OB_SET_SqlMap your class name? Does that class have a
selectObSetByPrimaryKey() method?

classpath* is the correct syntax. Without the * Spring will not
continue searching other jar files.

AntPort

unread,
Oct 21, 2010, 7:14:43 AM10/21/10
to mybatis-user
I did some code refactoring, and now I'm using namespaces for calling
the sql statements instead of mapnames (wich worked fine untill we
seperated one big project into o few smaller ones)
This is a sample map (ABCNEW_A_PRODUCT_SqlMap.xml)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
<mapper namespace="hr.abcinfo.dao.abcnew.ProductDao">

<cache eviction="FIFO" flushInterval="60000" size="512"
readOnly="true" />

<resultMap id="ProductMap"
type="hr.abcinfo.domain.abcnew.ProductPojo">

<result column="sProduct" property="sProduct" jdbcType="VARCHAR" />
<result column="nProduct" property="nProduct" jdbcType="VARCHAR" />
<result column="change" property="change" jdbcType="VARCHAR" />
</resultMap>

<sql id="selectList">
S_Product AS sProduct,
N_Product AS nProduct,
change AS
change
</sql>

<sql id="fromList">
A_Product a_Product
</sql>

<sql id="whereList">
S_Product =
#{sProduct}
</sql>

<select id="countProductFilter" parameterType="String"
resultType="int"
useCache="true" flushCache="false">
SELECT COUNT(*)
FROM
<include refid="fromList" />
${value}
</select>
<select id="selectProductFilter"
parameterType="hr.abcinfo.utility.ibatis.domain.DynamicQueryObject"
resultType="hr.abcinfo.domain.abcnew.ProductPojo" useCache="true"
flushCache="false">
SELECT
<include refid="selectList" />
FROM
<include refid="fromList" />
${whereCondition} ${orderBy}
</select>
<select id="selectProductByPrimaryKey"
parameterType="hr.abcinfo.domain.abcnew.ProductPojo"
resultType="hr.abcinfo.domain.abcnew.ProductPojo" useCache="true"
flushCache="false">
SELECT
<include refid="selectList" />
FROM
<include refid="fromList" />
WHERE
<include refid="whereList" />
</select>
<delete id="deleteProduct"
parameterType="hr.abcinfo.domain.abcnew.ProductPojo"
flushCache="true">
DELETE
FROM A_Product
WHERE S_Product = #{sProduct}
</delete>
<update id="updateProduct"
parameterType="hr.abcinfo.domain.abcnew.ProductPojo"
flushCache="true">
UPDATE A_Product SET
N_Product = #{nProduct,jdbcType=VARCHAR},
change = #{change,jdbcType=VARCHAR}
WHERE S_Product = #{sProduct,jdbcType=VARCHAR}
</update>
<insert id="insertProduct"
parameterType="hr.abcinfo.domain.abcnew.ProductPojo"
flushCache="true">
INSERT INTO A_Product
(S_Product, N_Product, change)
VALUES
(#{sProduct,jdbcType=VARCHAR},#{nProduct,jdbcType=VARCHAR},
#{change,jdbcType=VARCHAR})
</insert>

<select id="printProductFilter"
parameterType="hr.abcinfo.utility.ibatis.domain.DynamicQueryObject"
resultType="hr.abcinfo.domain.abcnew.ProductPojo" useCache="true"
flushCache="false">
SELECT
<include refid="selectList" />
FROM
<include refid="fromList" />
${whereCondition} ${orderBy}
</select>
<select id="changeProduct" resultType="String">
SELECT change_fa() FROM
DUAL
</select>
</mapper>




...and this is DaoImpl class...


@Repository("ProductDao")
public class ProductDaoImpl extends SqlSessionDaoSupport implements
ProductDao {

private DaoHandler abstractDao;

@Autowired
public ProductDaoImpl(DaoHandler abstractDao) {
super();
this.abstractDao = abstractDao;
}

@Override
public int count(ArrayList<LookUpPojo> filter) {

Integer backCount = countFilter(abstractDao.setWhere(filter,
ProductPojo.class));
return backCount;

}

@Override
public int countFilter(String whereSql) {
Integer backCount = (Integer) getSqlSessionTemplate().selectOne(
"hr.abcinfo.dao.abcnew.ProductDao.countProductFilter", whereSql);

return backCount;
}

@Override
public void delete(ProductPojo product) {
getSqlSessionTemplate().insert(
"hr.abcinfo.dao.abcnew.ProductDao.deleteProduct", product);

}

@Override
public void insert(ProductPojo product) {
getSqlSessionTemplate().insert(
"hr.abcinfo.dao.abcnew.ProductDao.insertProduct", product);

}

@Override
public List<ProductPojo> print(ArrayList<LookUpPojo> filter,
ArrayList<SortPojo> sort, String reportName, String reportType) {
List<ProductPojo> product = getSqlSessionTemplate().selectList(
"hr.abcinfo.dao.abcnew.ProductDao.printProductFilter",
abstractDao.setDynamicData(filter, sort, ProductPojo.class));

return product;
}

@Override
public List<ProductPojo> printFilter(DynamicQueryObject
dynamicQueryObject) {
List<ProductPojo> product = getSqlSessionTemplate().selectList(
"hr.abcinfo.dao.abcnew.ProductDao.printProductFilter",
dynamicQueryObject);

return product;
}

@Override
public List<ProductPojo> select(ArrayList<LookUpPojo> filter,
ArrayList<SortPojo> sortPojos, RowBounds rowBounds) {

List<ProductPojo> product = selectFilter(abstractDao.setDynamicData(
filter, sortPojos, ProductPojo.class), rowBounds);

return product;
}

@Override
public List<ProductPojo> selectByPrimaryKey(ProductPojo product) {
return getSqlSessionTemplate().selectList(
"hr.abcinfo.dao.abcnew.ProductDao.selectProductByPrimaryKey",
product);
}

@Override
public List<ProductPojo> selectFilter(DynamicQueryObject
dynamicQueryObject,
RowBounds rowBounds)n {
List<ProductPojo> product = getSqlSessionTemplate().selectList(
"hr.abcinfo.dao.abcnew.ProductDao.selectProductFilter",
dynamicQueryObject, rowBounds);
return product;

}

@Override
public void update(ProductPojo product) {
getSqlSessionTemplate().update(
"hr.abcinfo.dao.abcnew.ProductDao.updateProduct", product);

}

@Override
public String uradio() {
return (String) getSqlSessionTemplate().selectOne(
"hr.abcinfo.dao.abcnew.ProductDao.uradioProduct");
}

@Override
public double max(ArrayList<LookUpPojo> filter)
throws PersistenceException, ClassNotFoundException,
NoSuchFieldException, IOException, SQLException {
double max = maxFilter(abstractDao.setWhere(filter,
ProductPojo.class));

return max;
}

@Override
public double maxFilter(String whereSql) {
double max = (Double) getSqlSessionTemplate().selectOne(
"hr.abcinfo.dao.abcnew.ProductDao.maxProductFilter", whereSql);

return max;
}

}

I'm still having the same error:

org.mybatis.spring.MyBatisSystemException: SqlSession operation;
nested exception is
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause:
java.lang.IllegalArgumentException: Mapped Statements collection does
not contain value for
hr.abcinfo.dao.abcnew.ProductDao.countProductFilter
### Cause: java.lang.IllegalArgumentException: Mapped Statements
collection does not contain value for
hr.abcinfo.dao.abcnew.ProductDao.countProductFilter

(Other errors are cropped)


What am I doing wrong ??

Antonio

Hunter

unread,
Oct 21, 2010, 8:45:44 AM10/21/10
to mybatis-user
I don't see anything immediately wrong with your code. Let me try and
put together a simpler example we can both work from.

Can you also add this line in one of your DAO impls so we can get the
output?

getSqlSessionTemplate().getSqlSessionFactory().getConfiguration().getMappedStatementNames()

Probably the best place is right before you call
getSessionTemplate().xxx This will give us the names that MyBatis is
configured with. I am guessing something is wrong with the names being
assigned.
> ...
>
> read more »
Message has been deleted

AntPort

unread,
Oct 22, 2010, 5:22:10 AM10/22/10
to mybatis-user
Ok, so this is what I get when I output
getSqlSessionTemplate().getSqlSessionFactory().getConfiguration().getMappedStatementNames()

[insertCurrency, printCurrencyFilter,
hr.abcinfo.dao.abcnew.CurrencyDao.selectCurrencyFilter,
hr.abcinfo.dao.abcnew.CurrencyDao.selectCurrencyByPrimaryKey,
hr.abcinfo.dao.abcnew.CurrencyDao.changeCurrency,
hr.abcinfo.dao.abcnew.CurrencyDao.deleteCurrency, updateCurrency,
hr.abcinfo.dao.abcnew.CurrencyDao.printCurrencyFilter,
countCurrencyFilter, selectCurrencyByKrtVal,
hr.abcinfo.dao.abcnew.CurrencyDao.updateCurrency, deleteCurrency,
selectCurrencyFilter,
hr.abcinfo.dao.abcnew.CurrencyDao.countCurrencyFilter,
selectCurrencyByPrimaryKey,
hr.abcinfo.dao.abcnew.CurrencyDao.selectCurrencyByKrtVal,
changeCurrency, hr.abcinfo.dao.abcnew.CurrencyDao.insertCurrency]

...this is the order that maps get parsed:

DEBUG 10-22 10:18:25 Parsed mapper file: 'URL [jar:file:/home/pron/.m2/
repository/hr/abcinfo/ruggiero/basics/java-basics-dao/0.0.1-SNAPSHOT/
java-basics-dao-0.0.1-SNAPSHOT.jar!/config/sqlmapconfigs/
ABCNEW_A_CURRENCY_SqlMap.xml]' (JakartaCommonsLoggingImpl.java:27)
DEBUG 10-22 10:18:25 Parsed mapper file: 'URL [jar:file:/home/pron/.m2/
repository/hr/abcinfo/ruggiero/basics/java-basics-dao/0.0.1-SNAPSHOT/
java-basics-dao-0.0.1-SNAPSHOT.jar!/config/sqlmapconfigs/
STORED_PROCEDURE_COMMITER_SqlMap.xml]' (JakartaCommonsLoggingImpl.java:
27)
DEBUG 10-22 10:18:25 Parsed mapper file: 'URL [jar:file:/home/pron/.m2/
repository/hr/abcinfo/ruggiero/basics/java-basics-dao/0.0.1-SNAPSHOT/
java-basics-dao-0.0.1-SNAPSHOT.jar!/config/sqlmapconfigs/
ABCNEW_A_USR_SET_SqlMap.xml]' (JakartaCommonsLoggingImpl.java:27)
(the rest is cropped)

...seems that only the first map is loaded into mapped statements, all
the others are somehow ignored...
I also see duplicates (don't know if that is supposed to be so), with
and without namespaces attached...


A bit more info about my config:

Sqlmaps are located in dao project inside src/main/resources/config/
sqlmapconfigs.
Main tests are in service project which has the dependency on dao.
I created some unit tests using direct dao layer and connection to
live test database (not with mocks), and when I run them through maven
i get only one map loaded,
but when starting tests one at the time through eclipse JUnit, all the
statements are there, and the tests pass without errors...

Antonio
> ...
>
> read more »

AntPort

unread,
Oct 22, 2010, 11:08:08 AM10/22/10
to mybatis-user
I forgot to mention that the configuration via config file with all
the mapper entries works fine...

Antonio
> ...
>
> read more »

Eduardo

unread,
Oct 22, 2010, 11:21:46 AM10/22/10
to mybatis-user
Antonio, could you mail some of your code to us so we can reproduce
the error?
> ...
>
> leer más »

Hunter

unread,
Oct 24, 2010, 2:26:45 PM10/24/10
to mybatis-user
The duplicates, some without the package name is expected. This allows
you to use the shorter name, without the namespace, if your sql names
are unique across namespaces.


You have all your MyBatis mapper xml files in the same package,
correct? But the same package is in more than 1 jar file?
I am wondering if MyBatis's ResolverUtil (used by
MapperScannerPostProcessor) is not finding the same classpath in
multiple jars; maybe it stops after the first match.

Could you try moving the config files in one jar to a different
package and specifying both packages in your Spring config? You can
separate the 2 package names with commas. I know this isn't a solution
for you in the long term, but if this works, then we know where to
look to try and fix this.
> ...
>
> read more »

AntPort

unread,
Oct 25, 2010, 8:46:04 AM10/25/10
to mybatis-user
I entered a few debug lines in the source of SqlSessionFactoryBean
and
it seems that program never enters inside this part:

if (mapperLocation instanceof ClassPathResource) {
path = ClassPathResource.class.cast(mapperLocation)
.getPath();
}

Seems like mapperLocation is not instance of (ClassPathResource) (??),
though the debug output shows something like this:
DEBUG 10-25 14:23:41 Parsed mapper file: 'URL [jar:file:/home/ron/.m2/
repository/hr/abcinfo/ruggiero/basics/java-basics-dao/0.0.1-SNAPSHOT/
java-basics-dao-0.0.1-SNAPSHOT.jar!/config/sqlmapconfigs/
ABCNEW_A_CURRENCY_SqlMap.xml]' (JakartaCommonsLoggingImpl.java:27)

and then, this line of the code:

path = mapperLocation.getURI().getPath();

..returns null...

If I try to cast mapperLocation to ClassPathResource, I get
ClassCastException...

Antonio
> ...
>
> read more »
Message has been deleted

Eduardo

unread,
Oct 25, 2010, 3:54:59 PM10/25/10
to mybatis-user
Ok Antonio, you got it. There is a bug there. If the resource is a
jar .toString() gives this:

URL [jar:file:/C:/Documents%20and%20Settings/eduardo/.m2/repository/
org/mybatis/prueba/0.0.1/prueba-0.0.1.jar!/sample/UserMapper2.xml]

and mapperLocation.getURI().getPath() is null and that is a good
reason for it to fail.


On 25 oct, 19:23, Eduardo <eduardo.macar...@gmail.com> wrote:
> Well, that should not be a problem.
>
> It does not matter that the resource is not a ClassPathResource,
> unless you have mapper interfaces also (there is a comment near that
> code that explains the problem).
>
> Anyway when that happens MyBatis throws a Exception and thats not your
> case.
> ...
>
> leer más »

Eduardo

unread,
Oct 25, 2010, 4:55:54 PM10/25/10
to mybatis-user
Antonio, we have uploaded a fix for this to svn.

http://mybatis.googlecode.com/svn/sub-projects/mybatis-spring/trunk/

could you check it out and try it? Otherwise tell me and will mail the
jar to you.
> ...
>
> leer más »

AntPort

unread,
Oct 26, 2010, 6:10:32 AM10/26/10
to mybatis-user
Yes, the builds and tests on my projects are working fine now!

Thanks!
Antonio
> ...
>
> read more »

Simone Tripodi

unread,
Oct 26, 2010, 6:36:45 AM10/26/10
to mybati...@googlegroups.com
Cool, good news!!! Gracias Antonio!!!
Saludos,
Simo

http://people.apache.org/~simonetripodi/
http://www.99soft.org/

Reply all
Reply to author
Forward
0 new messages