Why is Mybatis mapper scanner picking up wrong class - spring

I use Spring with Mybatis. I have it configured to scan for mappers in my whole project and I assumed it determined a mapper because it found an XML file which has reference to a java interface.
But this is proven incorrect today because I had to add a new interface which is not a mapper class and Mybatis thinks it is, so it is causing problems in my app due to this error:
Mapped Statements collection does not contain value for com.blah.MyInterface.someMethod
com.blah.MyInterface is just a simple interface which I needed to be included in Spring context so I gave it the #Component tag. Is that the wrong tag to use? Is that where the confusion comes from?
I just needed to create this interface so that I can have a proxy wrap my database calls in one place where I can put a #Transactional tag, since Spring ignores it when it is in my Controller method.
Sample code
package com.blah.something;
#Component public interface MyInterface {
public void someMethod( SomeObject obj) throws Exception;
}
package com.blah.something;
public class MyImplementation implements MyInterface {
#Transactional
public void someMethod( SomeObject obj) throws Exception {
... do a whole bunch of stuff
}
}
I dont want this included in the MyBatis mappers!
Edit: added the mybatis config xml as requested:
<?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>
<settings>
<setting name="lazyLoadingEnabled" value="false" />
<setting name="defaultStatementTimeout" value="60"/>
</settings>
<typeAliases>
<typeAlias alias="StripTrailingZerosBigDecimalTypeHandler" type="com.blah.typehandlers.StripTrailingZerosBigDecimalTypeHandler"/>
</typeAliases>
</configuration>
This is the part of my spring xml config which calls the mybatis mapper scanner:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.blah" />
</bean>
So I set it to scan the whole project which includes my interface above but I can't imagine it just grabs every single interface and considers them all mappers!
In my debug log I see mybatis picking up my interface:
12/9/13 11:18:44 904 [org.mybatis.spring.mapper.MapperScannerConfigurer$Scanner.findCandidateComponents:4125] - Scanning file [D:\Weblogic\wls11\domains\ldapdomain\autodeploy\default\WEB-INF\classes\com\blah\MyInterface.class]
12/9/13 11:18:44 904 [org.mybatis.spring.mapper.MapperScannerConfigurer$Scanner.findCandidateComponents:4125] - Identified candidate component class: file [D:\Weblogic\wls11\domains\ldapdomain\autodeploy\default\WEB-INF\classes\com\blah\MyInterface.class]
12/9/13 11:18:44 904 [org.mybatis.spring.mapper.MapperScannerConfigurer$Scanner.findCandidateComponents:4125] - Scanning file [D:\Weblogic\wls11\domains\ldapdomain\autodeploy\default\WEB-INF\classes\com\blah\MyImplementation .class]
12/9/13 11:18:44 904 [org.mybatis.spring.mapper.MapperScannerConfigurer$Scanner.findCandidateComponents:4125] - Ignored because not a concrete top-level class: file [D:\Weblogic\wls11\domains\ldapdomain\autodeploy\default\WEB-INF\classes\com\blah\MyImplementation .class]
There is no XML for this interface, there is no mapper namespace for it, it's just a plain old regular interface and MyBatis should not be thinking it is a mapper service

Ok it looks like MyBAtis scanner does indeed take every interface, it does not have any "smarts" in it to identify mapper interfaces as I thought it would - based on finding matching XML or namespaces. I had to add a filter to the mapper configuration and then introduce a new annotation to annotate my mapper interfaces.

Related

How to implement SmartLifecycleRoleController in Spring Integration application

I am writing client-server application on base of int-ip:tcp endpoints. Code written in Kotlin.
Application contains two tcp clienst which should make connection to server in sertain sequence,
one after each other, when first cleint already establised connection to server and made some initialisation.
As a solution for this synchronisation I suppose to use a SmartLifecycleRoleController to start group of endpoints of a dependent tcp client.
To that end in depended (second) client I add role="rcCluster" and auto-startup="false" attributes to tcp-outbound-channel-adapter and tcp-inbound-channel-adapter
<int-ip:tcp-connection-factory id="rcClientConnectionFactory"
type="client"
host="${tdirelay.host}"
port="${tdirelay.rcPort}"
single-use="false"
so-timeout="10000"
so-keep-alive="false"
serializer="rawSerializerDeserializer"
deserializer="rawSerializerDeserializer"
ssl-context-support="sslContext"/>
<int-ip:tcp-outbound-channel-adapter id="rcOutBoundAdapter"
channel="rcPacketQueueChannel"
phase="5000"
connection-factory="rcClientConnectionFactory"
role="rcCluster"
auto-startup="false"
/>
<int-ip:tcp-inbound-channel-adapter id="rcInboundAdapter"
channel="rcFromServer"
client-mode="true"
retry-interval="5000"
connection-factory="rcClientConnectionFactory"
role="rcCluster"
auto-startup="false"
/>
The leading (first) tcp client uses interceptor endpoint to make a prologue exhange with server in accordance with the protocol:
<bean id="dcInterceptorFactoryChain"
class="org.springframework.integration.ip.tcp.connection.TcpConnectionInterceptorFactoryChain">
<property name="interceptors">
<array>
<bean class="com.tcpclient.DirectCannel.DCConnectionInterceptorFactory">
</bean>
</array>
</property>
</bean>
<int-ip:tcp-connection-factory id="dcClientConnectionFactory"
type="client"
host="${tdirelay.host}"
port="${tdirelay.dcPort}"
single-use="false"
so-timeout="10000"
so-keep-alive="false"
interceptor-factory-chain="dcInterceptorFactoryChain"
serializer="rawSerializerDeserializer"
deserializer="rawSerializerDeserializer"
ssl-context-support="sslContext"
/>
I am planning to call startLifecyclesInRole(String role) and stopLifecyclesInRole(String role) methods of the SmartLifecycleRoleController inside my Interceptor class.
So, I added #Autowired private val roleController: SmartLifecycleRoleController to InterceptorFactory as explained in spring-integration/docs
My InterceptorFactory is:
class DCConnectionInterceptorFactory() : TcpConnectionInterceptorFactory, ApplicationEventPublisherAware {
#Autowired
private val roleController: SmartLifecycleRoleController? = null
#Volatile
private var applicationEventPublisher: ApplicationEventPublisher? = null
override fun setApplicationEventPublisher(applicationEventPublisher: ApplicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher
}
override fun getInterceptor(): TcpConnectionInterceptorSupport {
return DCConnectionInterceptor(this.applicationEventPublisher!!, roleController!!)
}
}
IntelliJ IDEA gives warning:
Could not autowire. No beans of 'SmartLifecycleRoleController' type found
And building gives error:
[task-scheduler-2] ERROR org.springframework.integration.ip.tcp.connection.ClientModeConnectionManager - Could not establish connection using dcClientConnectionFactory, host=localhost, port=9001
kotlin.KotlinNullPointerException
at com.tcpclient.DirectCannel.DCConnectionInterceptorFactory.getInterceptor(DCConnectionInterceptorFactory.kt:25)
I suppose that I need to define SmartLifecycleRoleController type bean in xml configuration file
(that is not mentioned in the docs: https://docs.spring.io/spring-integration/docs/4.3.4.RELEASE/reference/html/messaging-endpoints-chapter.html#endpoint-roles).
The constructor of this class has arguments:
public SmartLifecycleRoleController(List roles, List lifecycles)
which I do not know how to fill in my case in xml file:
If you know how to do this, please provide a live example of using bean of the SmartLifecycleRoleController class in the xml configuration file.
First of all it would be better do not use #Autowired there at all, since it isn't clear if an annotation configuration is enabled in your application context or not. Just because you show only XML config for Spring.
Therefore your DCConnectionInterceptorFactory should have a setter for that roleController property instead. Or what is the proper way to declare Java bean properties in Kotlin...
Then you need to use something like this in your XML config:
<bean class="com.tcpclient.DirectCannel.DCConnectionInterceptorFactory">
<property name="roleController" ref="integrationLifecycleRoleController"/>
</bean>
The Framework creates for your a SmartLifecycleRoleController automatically and registers it with the mentioned IntegrationContextUtils.INTEGRATION_LIFECYCLE_ROLE_CONTROLLER bean name.
Please, raise a JIRA on the matter to improve documentation to make since much cleaner.
Your approach, by the way, is correct.

what is the usage of new keyword while using java configuration in spring

I have a question around the usage of new keyword being used when using java configuration in spring. What is the need of using new keyword
Refer below mentioned example:
Code implemented using Java Config
#Configuration
public class HelloWorldConfig {
#Bean
public HelloWorld helloWorld(){
return new HelloWorld();
}
}
The above code will be equivalent to the following XML configuration
<beans>
<bean id = "helloWorld" class = "com.test.HelloWorld" />
</beans>
In XML config, we do not use new keyword whereas in java config we are using new keyword. can someone please explain the difference
In the XML configuration, you explain to the system what class should be instanciated (there is a "new" but it is behind the scene) but in the Java Config you actually have to return an instance so that is why we use the 'new' keyword. 'new' simply creates an instance of your class.
The two examples shown in question are not really equivalent.
What the
<beans>
<bean id="helloWorld"
class="com.test.HelloWorld" />
</beans>
really does, is it tells Spring to instantiate class com.test.HelloWorld, and name the resulting bean "helloWorld".
Then the java-config approach is not really doing this. Instead this follows the factory-method pattern, when we tell Spring, that the return value of the method is the bean, and the method name is the name of that bean.
An equivalent of that in XML would be the mentioned factory-method approach, which in this case would look something like this:
<beans>
<bean id="helloWorldConfig"
class="com.test.HelloWorldConfig" />
<bean id="helloWorld"
factory-bean="helloWorldConfig"
factory-method="helloWorld" />
</beans>
Note that there are several approaches to factory-method. In the above, we are assuming, the `helloWorldConfig" is the factory, and we're specifying the method on that bean. Theare are cases with static factory methods too. See here for more examples.
<beans>
<bean id = "helloWorld" class = "com.test.HelloWorld" />
</beans>
This XML configurations tells Spring to "create an instance of com.test.HelloWorld and put it in the bean context with bean id helloWorld".
#Configuration
public class HelloWorldConfig {
#Bean
public HelloWorld helloWorld(){
return new HelloWorld();
}
}
In this Java configuration, we are returning an instance of com.test.HelloWorld. Because of the #Bean annotation, this instance is put into the bean context. As no specific bean id is given, the bean id is derived from the method hellowWorld() and thus becomes helloWorld.
As you can see, both configurations require an instance of com.test.HelloWorld. The XML configuration implicitly creates the instance whereas in the Java configuration you have to explicitly do it yourself.

Context vs context - setting ignore-unresolvable="true" - PropertyPlaceholderConfigurer

Problem
I'm trying to set ignore-unresolvable="true".
I have found the answer https://stackoverflow.com/a/11773267/1688441 from the question how to define not mandatory property in spring? .
The example they show is:
<context:property-placeholder ignore-unresolvable="true" ... />
However, in the project I have inherited we have a project file called project.xml that contains Resource definitions with a Context tag.
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource />
<ResourceLink />
<Resource />
</Context>
Note: The resources have been removed
When I edit the Context tag to add ignore-resolvable everything breaks and not even my DataSource resource is read. Anyone have any ideas?
I tried the following:
<Context:property-placeholder ignore-unresolvable="true">
Possibly related:
spring PropertyPlaceholderConfigurer and context:property-placeholder
It turns out that in the specific project a class based configuration was being used instead of XML. I found the following class to which I added setIgnoreUnresolvablePlaceholders(false) in the method that returns PropertySourcesPlaceholderConfigurer:
#Configuration
#ComponentScan
#EnableWebMvc
#EnableAsync
#EnableScheduling
#PropertySource(value = {"classpath:appProp.properties"})
#Import({ExternalizeConfiguration.class, AppApplication.class,
AppPersistenceApplication.class, ConnectBoIntegrationApplication.class})
public class AppWebApplication extends WebMvcConfigurerAdapter {
...Other Code...
/**
* Bean required for Value annotation
*/
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer test = new PropertySourcesPlaceholderConfigurer();
test.setIgnoreUnresolvablePlaceholders(false);
return test;
}
}
So my understanding is that annotating this method as #Bean causes the method to execute whenever an object of type PropertySourcesPlaceholderConfigurer is auto-wired. In this way, we control which instance is used and what params are set on it.

Scan specific packages for Spring AOP

I'm trying to use AOP into an existing (big) Spring project. The thing is that I don't want Spring to create proxies for all the objects in the ApplicationContext, mainly for performance but also because there are final classes that I cannot modify.
I've tried to make Spring search only inside "com.foo.bar.*" by defining the following aspect:
com.baz.MyAspect
#Aspect
public class MyAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(MyAspect.class);
#Before("within(com.foo.bar.*) && " +
"execution(* com.foo.bar.MyController.handleRequest(..))")
public void getData() {
// Nothing yet
}
}
And I've added this lines to the configuration:
<?xml version="1.0" encoding="utf-8"?>
<beans ...>
<aop:aspectj-autoproxy proxy-target-class="true" />
<bean id="myAspect" class="com.baz.MyAspect"/>
</beans>
But when I run the app, I get the following Exception:
Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.foobar.FinalController]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class com.foobar.FinalController
So it seems that Spring is scanning packages other than the ones defined in the within expression. I would like to know whether there is a way to specify the packages to be scanned or any other way to solve this problem.
yes you can define your point cut like this
the execution of any method defined in the bar package or a sub-package:
execution(* com.foo.bar...(..)) click here for detail

Spring Configuration of Custom Apache Camel Data Format

I am using Apache Camel 2.9.2 and Spring 3.0.6.RELEASE. I am trying to use a custom DataFormat to marshal and unmarshal Camel messages. I want to configure my custom DataFormat into one of my routes using Spring.
Apache Camel's documentation states that in order to hook up my custom Data Format to a route in Spring I simply need to declare my custom DataFormat as a bean and reference it inside of my Spring route like so:
<marshal>
<custom ref="myCustomDataFormat"/>
</marshal>
http://camel.apache.org/custom-dataformat.html
So I have the following setup:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
">
<bean id="myCustomDataFormat" class="com.test.CustomDataFormat"/>
<!-- Camel Context -->
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="file:C:/test?initialDelay=4000&delay=1000"/>
<marshal>
<custom ref="myCustomDataFormat"/>
</marshal>
<to uri="file:C:/test2"/>
</route>
</camelContext>
</beans>
But when I try to start Camel, I get the following nasty error:
org.springframework.beans.ConversionNotSupportedException: Failed to convert value of type 'com.test.CustomDataFormat' to required type 'org.apache.camel.model.DataFormatDefinition'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [com.test.CustomDataFormat] to required type [org.apache.camel.model.DataFormatDefinition]: no matching editors or conversion strategy found
My Data Format is defined as follows:
package com.test;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.camel.Exchange;
import org.apache.camel.spi.DataFormat;
public class CustomDataFormat implements DataFormat {
/* (non-Javadoc)
* #see org.apache.camel.spi.DataFormat#marshal(org.apache.camel.Exchange, java.lang.Object, java.io.OutputStream)
*/
#Override
public void marshal(Exchange exchange, Object graph, OutputStream stream)
throws Exception {
System.out.println("Marshal");
byte[] bytes = exchange.getContext().getTypeConverter().mandatoryConvertTo(byte[].class, graph);
stream.write(bytes);
}
/* (non-Javadoc)
* #see org.apache.camel.spi.DataFormat#unmarshal(org.apache.camel.Exchange, java.io.InputStream)
*/
#Override
public Object unmarshal(Exchange exchange, InputStream stream)
throws Exception {
System.out.println("Unmarshal");
byte[] bytes = exchange.getContext().getTypeConverter().mandatoryConvertTo(byte[].class, stream);
return bytes;
}
}
I know that my CustomDataFormat implementation is correct because I created the following test route in Java and it worked flawlessly
package com.test;
import org.apache.camel.spring.SpringRouteBuilder;
public class TestFormatRoute extends SpringRouteBuilder {
/* (non-Javadoc)
* #see org.apache.camel.builder.RouteBuilder#configure()
*/
#Override
public void configure() throws Exception {
from("file:C:/test?initialDelay=4000&delay=1000").unmarshal(new CustomDataFormat()).to("file:C:/test2");
}
}
What am I missing?
Thanks
Update
After letting Camel completely start up after receiving this error I found to my disbelief that my custom data format actually does work in the route that I created. I'm not sure what process is attempting to parse my custom data format and failing but it is apparently not the same process parsing the data format to put into my route.
This solves the functional requirement of the data format, but it does not explain why I am receiving this error.
I have also confirmed that it was not the name of my data format (CustomDataFormat) that was causing the issue. Renaming my DataFormat to a unique name (MerlinDataFormat) did not fix the error.
I still would like to know why I am receiving this error since large blocks of ugly red errors in my console and log files aren't exactly appealing.
Thanks again.
It turned out to be a pretty simple solution (and one that I admit should have been easy to see). There are actually two ways to go about solving this issue, one of them using only spring and one of them requiring an additional java class.
Solution 1
Create a new class extending DataFormatDefinition which has the same properties as your custom DataFormat. Override the configureDataFormat() method to set all of the properties of the underlying DataFormat. Add constructor(s) to set the underlying DataFormat as an instance of your CustomDataFormat. Now you should be able to create an instance of your DataFormatDefinition in spring and reference it when marshaling or unmarshaling.
Solution 2 (Quick & Dirty)
In spring, create a new DataFormatDefinition bean and set it's dataFormat property as a reference to your DataFormat spring bean. Now you should be able to reference your DataFormatDefinition bean when marshaling or unmarshaling.
Not really sure what's wrong with your example, it seems just fine. Can you post your code for the data format? Are you implementing org.apache.camel.spi.DataFormat correctly?
I just set up this example with Camel 2.9.2 and it works like a charm. The Custom data format is the one from Camel documentation/source code.
<bean id="mySweetDf" class="com.example.MySweetDf"/>
<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="file:C:/temp/test?initialDelay=4000&delay=1000"/>
<marshal>
<custom ref="mySweetDf"/>
</marshal>
<convertBodyTo type="java.lang.String"/>
<to uri="file:C:/temp/test2"/>
</route>
</camelContext>
data format java file:
package com.example;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.camel.Exchange;
import org.apache.camel.spi.DataFormat;
public class MySweetDf implements DataFormat {
public void marshal(Exchange exchange, Object graph, OutputStream stream) throws Exception {
byte[] bytes = exchange.getContext().getTypeConverter().mandatoryConvertTo(byte[].class, graph);
String body = reverseBytes(bytes);
stream.write(body.getBytes());
}
public Object unmarshal(Exchange exchange, InputStream stream) throws Exception {
byte[] bytes = exchange.getContext().getTypeConverter().mandatoryConvertTo(byte[].class, stream);
String body = reverseBytes(bytes);
return body;
}
private String reverseBytes(byte[] data) {
StringBuilder sb = new StringBuilder(data.length);
for (int i = data.length - 1; i >= 0; i--) {
char ch = (char) data[i];
sb.append(ch);
}
return sb.toString();
}
}
UPDATE
Just tried you code. Seems to work as well. Created a fresh camel 2.9.2 project via mvn archetype 168: remote -> org.apache.camel.archetypes:camel-archetype-spring (Creates a new Camel project with added Spring DSL support.). This does only include camel-core and camel-spring dependencies, nothing else.
Then replaced camel-context.xml with your xml and added your data format code in the java directory. A run with "mvn camel:run" copied the file and printed "marshal" in the log.
[pache.camel.spring.Main.main()] SpringCamelContext INFO Route: route1 started and consuming from: Endpoint[file://C:/test?delay=1000&initialDelay=4000]
[pache.camel.spring.Main.main()] SpringCamelContext INFO Total 1 routes, of which 1 is started.
[pache.camel.spring.Main.main()] SpringCamelContext INFO Apache Camel 2.9.2 (CamelContext: camel-1) started in 0.808 seconds
Marshal
Are you sure you have all dependencies setup correctly and not some .jar file that messes things up with Data formats?
UPDATE2
Okay, I think I have an idea what it is:
http://camel.apache.org/maven/current/camel-core/apidocs/org/apache/camel/model/dataformat/CustomDataFormat.html
Camel already have a class named as your data format. You should try rename it to something else. CustomDataFormat extends org.apache.camel.model.DataFormatDefinition which is referred to in your error. Java should handle this, since it's two different namespaces, but there might be some issue in your project setup that causes this conflict. Try to rename the data format and see if that solves the problem.
I too was facing the same issue with camel 2.10.0. If you provide the ref with an instance of type org.apache.camel.model.DataFormatDefinition everything works fine!! I can see two classes for xmljson conversion --> XmlJsonDataFormat implementing both DataFormat and DataFormatDefinition.
I solved the same issue that I too was facing.
Implemented a class extending DataFormatDefintion - which in it's configureDataFormat method sets injectable properties for the class that extends DataFormat (in your case this is CustomDataFormat).
I used XmlJson conversion as a template to solve.

Resources