Skip to content

auto-configuration and config location conflict. #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
jcbvm opened this issue Mar 28, 2016 · 26 comments
Closed

auto-configuration and config location conflict. #39

jcbvm opened this issue Mar 28, 2016 · 26 comments
Assignees
Labels

Comments

@jcbvm
Copy link

jcbvm commented Mar 28, 2016

Currently I've an issue with setting up my project.

When I set the config property in my application.properties, MybatisAutoConfiguration still scans the project for possible mappers, causing all my interfaces in that package becoming a mapper file.

When debugging, I see the config check being done after the auto configuration is scanning my package. This seems like a bug to me.

@eddumelendez
Copy link
Member

Hi @jcbvm are you talking about this?
screen shot 2016-03-30 at 8 00 11 pm

/cc @emacarron

@jcbvm
Copy link
Author

jcbvm commented Mar 30, 2016

@eddumelendez I'm talking about the AutoConfiguredMapperScannerRegistrar class in the class MybatisAutoConfiguration. The method registerBeanDefinitions is automatically scanning the base package. Not sure if this is intended, it seems so, because the documentation of the MapperScannerRegistrarNotFoundConfiguration class says:

"If it is not used, however, then this will bring in a bean registrar and automatically register components based on the same component-scanning path as Spring Boot itself."

My question is why it is by default scanning the package if the mappers are hard coded in the config file.

@eddumelendez
Copy link
Member

It is working as spring-boot itself, if you are working with jpa in spring-boot yo don't need to put @Repository in your repositories classes, spring-boot does for you. The same happen with mybatis-spring-boot. The difference is, this project provided support for xml and java config. And as you can see there are some combinations that doesn't work as expected. I do not know if we can cover each case but, at the beginning, we add xml support to help people in the transition.

We can solve this removing the xml support :)
I will take a look with some ideas in my mind but take into account that now xml and java config will be working together in the next version.

Thanks

@jcbvm
Copy link
Author

jcbvm commented Mar 30, 2016

Thanks, for now I will move out any interface from the spring boot application package, so it will not get picked up as a mapper file.

@jcbvm jcbvm closed this as completed Mar 30, 2016
@emacarron
Copy link
Member

Hi,

@jcbvm If you add you mapper files using the mybatis-config.xml file, MyBatis will know about the mappers but spring don't. You need that your mappers are scanned so they can be injected afterwards. When using Spring we expect that mappers become spring beans and scanning is the heart of spring boot :)

I am not familiar with spring boot JPA. I will take a look to have a better opinion about this topic, but it indeed looks like a bug that all the interfaces in a project are registered as mappers.

You can bypass this by adding a @MapperScan annotation that lets you specify lots of filters.

But honestly I do not like to mix the usage of @MapperScan with spring boot. I would prefer to specify everyting in the application.properties. The AutoConfiguredMapperScannerRegistrar can take lots of parameters to make a finer search like for example the base package. This can be achieved easily by porting code from @MapperScan (MapperScannerRegistrar).

If you don't mind lets keep ths issue open because it is worth having a discussion about how this feature works.

@emacarron emacarron reopened this Mar 30, 2016
@emacarron
Copy link
Member

Hi again,

Does anybody know how can we read the MyBatisProperties bean in AutoConfiguredMapperScannerRegistrar???

@kazuki43zoo
Copy link
Member

@emacarron I don't know ...
Can use the MapperScannerConfigurer instead of ?

@emacarron
Copy link
Member

I don't think so. The registrar is the right bean to provide addional beans to a @configuration object. But looks like when it is executed the MyBatisProperties bean has not yet been created. I tried to get the object out of the beanFactory but it is not yet there :(

BTW I have just had a fast look at spring-boot-starter-data-jpa. Looks like by default it searches for classes that implements the interface Repository or are annotated with @RepositoryDefinition so seems that it won't get all the interfaces in the project but just the right ones.

See https://github.com/spring-projects/spring-data-commons/blob/master/src/main/java/org/springframework/data/repository/config/RepositoryComponentProvider.java#L71

super.addIncludeFilter(new InterfaceTypeFilter(Repository.class));
super.addIncludeFilter(new AnnotationTypeFilter(RepositoryDefinition.class, true, true));

Unfortunately MyBatis mappers are not marked (by design) so a good idea would be for example to search with a pattern like basepackage + "/**/mapper" so mappers are expected to be inside a package called whatever/mappers. The problem now is that this will break existing code. But I wount care too much about that given the project has just born.

emacarron added a commit to emacarron/spring-boot-starter that referenced this issue Mar 31, 2016
@emacarron
Copy link
Member

Have a look at this, what do you think?

emacarron@e4a2c4b

@kazuki43zoo
Copy link
Member

Can get property value via Environment instead of type-safe properties using EnvironmentAware ?

For example:

public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware,
        ImportBeanDefinitionRegistrar, ResourceLoaderAware , EnvironmentAware {
    // ...
    private Environment environment;
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
    // ...
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
            BeanDefinitionRegistry registry) {

        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

        try {
            String[] mapperPackages = StringUtils.tokenizeToStringArray(environment.getProperty("mybatis.mapper-packages"),
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            if(mapperPackages == null || mapperPackages.length == 0) {
                List<String> pkgs = AutoConfigurationPackages.get(this.beanFactory);
                for (String pkg : pkgs) {
                    log.debug("Found MyBatis auto-configuration package '" + pkg + "'");
                }
                if (this.resourceLoader != null) {
                    scanner.setResourceLoader(this.resourceLoader);
                }
                mapperPackages = pkgs.toArray(new String[pkgs.size()]);
            }
            scanner.registerFilters();
            scanner.doScan(mapperPackages);
        }
        catch (IllegalStateException ex) {
            log.debug("Could not determine auto-configuration "
                    + "package, automatic mapper scanning disabled.");
        }
    }
    // ...
}

What do you think ?

@kazuki43zoo
Copy link
Member

Probably, above my idea is not good ... (sorry ...)

@emacarron
Copy link
Member

Any idea is welcome @kazuki43zoo!! But I am afraid I already checked that and does not work.

Anyway, what about scanning only interfaces inside **/mapper packages. What do you think (@eddumelendez @jcbvm @kazuki43zoo) ?

@kazuki43zoo
Copy link
Member

@emacarron , If possible i hope to support **/repository package. I use Mapper interface instead of Repository of DDD design(Spring's @Repository).
What do you think ?

@emacarron
Copy link
Member

Sounds good. I will probably make sense to search in these package names by default:
**/mapper -> an opinionated mybatis tools should accept the "mybatis" main element
**/repository -> in case you prefer the spring jargon, though this may collide if you are also using JPA
**/persistence?? -> in case you copy & pasted from jpetstore-6? Should I better change the jpetstore-6?

Note that you yourself used the term "mapper" in the sample you make for gh-38:
https://github.com/kazuki43zoo/mybatis-spring-boot-gh-38

This is because the term mapper rocks!!! doesn't it?? ;)

Already added to the sample 0a2c27d

Note again that this will break existing code.

edit: I changed jpetstore-6 repo and the docs so the mappers package is now called "mapper"

@kazuki43zoo
Copy link
Member

I prefer the repository package 😄
I think this solution will should provide since a next minor release (e.g. 1.1.0).

@jcbvm
Copy link
Author

jcbvm commented Apr 1, 2016

From my opinion, no package should be scanned by default. The base package should be defined in the config of spring boot. Which is in line with the legacy xml config property <mybatis:scan base-package="org.mybatis.spring.sample.mapper" />

@emacarron
Copy link
Member

@jcbvm That makes sense when using raw spring but boot is supposed to use convention over configuration. In my opinion, an application should work by just adding a mapper and an autowired, with no further manual configuration.

Taking all interfaces as mappers will fulfill the objective but may cause too many side effects because you will have for sure interfaces that are not mappers.

OTOH I do agree in that we should be able to change the base package. Now that can only be done by adding a @MapperScan annotation to your SpringBoot main class. What is not so bad..

@emacarron emacarron added this to the 1.1.0 milestone Apr 1, 2016
@emacarron emacarron self-assigned this Apr 1, 2016
@jcbvm
Copy link
Author

jcbvm commented Apr 1, 2016

@emacarron That's truth, the question is how many configuration is allowed for using convention over configuration :P.

But I agree that it should be able to overwrite the defaults or even disable the whole scanning.

@emacarron
Copy link
Member

I would not expect that someone that uses spring boot would not want to use scanning, because that is in fact the main feature of boot! :)

Right now scanning can only be disabled by adding one dummy Mapper and a @MapperScan configured to find it. Otherwise, if no MapperFactoryBean is found in the context the auto-scan will happen.

@jcbvm
Copy link
Author

jcbvm commented Apr 2, 2016

@emacarron Well for example if you have some complex queries and you want to build a Map with all kind of parameters for the query, you'll most likely want to create this Map in the Mapper implementation class in which you call the SqlSession yourself. In this case scanning is not necessary.

@emacarron
Copy link
Member

Of course there are cases when you do not want your interface to be picked as a mapper. That is clear. But if you have a mapper (a mybatis interface with SQL annotations or an attached XML) then it should be always ok that it is autodiscovered.

The only situation I can imagine when auto-scanning does not work is when you have different datasources / sqlSessionFactories. That is a complex case. I would say it is good that simple cases work by default and complex cases require some deeper knowledge and finer tuning.

Anyway your comment reveals an interesting point. We should not scan in too many places so you can use other tecnologies without collision:

  • If you want daos then place them in a "dao" or "persistence" package
  • if you want to use JPA place objects in repository or repositories package.

So to scan into a repository package we should invert the filters that jpa is using. That will be more or less:

scanner.addExcludeFilter(new InterfaceTypeFilter(Repository.class));
scanner.addExcludeFilter(new AnnotationTypeFilter(RepositoryDefinition.class, true, true));

The problem is that Repository objects may not be in the classpath so we need to add some annoying code to check its existance and add the optional dependency to pom.xml.

@emacarron
Copy link
Member

@kazuki43zoo see commit f361971 with the exclusions. Not sure if this is a good approach. I am thinking that maybe we should not scan into "repository" to avoid the colision.... but please let me know your thoughts.

@eddumelendez
Copy link
Member

I am not agree hardcoding packages to exclude because I have seen projects with other conventions and maybe we are going to open the door to support to other package exclusions and manages this as a issues, which will be crazy.

By default, we can exclude .mappers package but can be replaced by a property in MybatisProperties to support the customization.

One extreme approach could be remove the mybatis.config but I have seen several people requesting to support this property in combination with the other ones.

@emacarron
Copy link
Member

Hi @eddumelendez !

More than exclusions we should decide which packages to include. Or... what sounds much better: which conventions are we going to use, following a "convention over configuration" strategy.

What I expect from spring boot is to have an aplication up and running with zero boilerplate. For that to happen, we need conventions that can be overriden only for special cases.

Right now, the mybatis boot starter module takes all the interfaces it founds in a project and registers them as mappers. Ok, that is not good but it is not so bad. The point is that we should be able to identify if an interface is a mapper or not.

The problem is that mappers are not annotated (the @Mapper annotation does not exist). We did this because we would like to build applications with no mybatis imports at all. So they way you select your mappers when using classic configuraiton like MapperScannerConfigurer or @MapperScan is by:

  • specifiying a base package
  • specifiying a marker interface
  • specifiying a marker annotation

It would be great to be able to set up this same configuraion in the boot's application.properties file but unfortunately I do no know how to do it!! (see former messages). And anyway we still have @MapperScan for that (that is what you used in the 1st versions before Josh Long's contribution)

So, my proposal is to use this convention: mappers are supposed to be interfaces held in a **/mapper package. For any other configuration, use the @MapperScan annotation that lets you configure everything.

What do you think?

@emacarron emacarron removed this from the 1.1.0 milestone Apr 5, 2016
@emacarron
Copy link
Member

Given that the original request is to disable scanning when there is a configuration set in the application.properties I will close this issue and start a new one about the scanner.

The reply for @jcbvm is that using a config file registers beans inside MyBatis but are not known to Spring, so in order to be able to inject mappers you need the mappers to be scanned also. Config and scanning can be used together.

There can be scenarios when a user does not want scanning at all for any reason, but we should handle that as an exception and give a solution that may be awful given that this is an edge case.

Lets follow the autoscan topic in #46

@emacarron
Copy link
Member

@jcbvm Jacob, can you please check issue #46 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants