最近在学习camel,公司之前做过的项目使用到了camel进行了很多工作。就连数据库的操作也是通过camel来完成的。至于用camel来操作数据库有什么优点,目前就我自己的体会来说,利用camel能简化CRUD操作service层的代码。没用camel以前,各个对象的CRUD操作我都会有对应的service去处理。即使这些service很多都只是简单地继承一个CrudServcie然后用泛型限制一下该service处理的实体对象。这样service的接口和实现类看上去很多,但是重复率极高。如果使用了camel,那么我们就可以用camel来写一个通用的service,这样不管你是什么实体类的操作,只要传入类型和要调用的方法名就可以了。由于涉及到公司机密,所以我不会贴出成熟的源代码,但是我可以提供一个自己的列子。我想只要从这个列子出发,稍作改进就能达到上文所提的效果。
例子的架构是这样的:springmvc camel mybatis
依赖由maven来管理,其pom.xml的内容请下载列子源代码查看。
springmvc的配置就不用贴出来了,随处可见。
这里重点讲一下spring跟配置文件里的一些配置项目,尤其是下面这一段:
<!-- 数据源配置 使用事务控制 --> <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8" /> <property name="username" value="root" /> <property name="password" value="fanly" /> <property name="defaultAutoCommit" value="false" /> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="myDataSource" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="myDataSource" /> <property name="configLocation" value="classpath:SqlMapConfig.xml" /> </bean> <bean id="required" class="org.apache.camel.spring.spi.SpringTransactionPolicy"> <property name="transactionManager" ref="transactionManager" /> <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED" /> </bean> <bean id="mybatis" class="org.apache.camel.component.mybatis.MyBatisComponent"> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean> <!-- 数据源配置结束 -->
需要注意的是最后一项的配置,通过MyBatisComponent类,camel就知道如何通过用户设置的路由来和mybatis进行交互了。这个MyBatisComponent实现了camel的component接口,至于component接口是用来干什么的,可以参考我的博文或者去查看官方手册。其实你可以把component简单里理解为camelContext和其他系统通信的标准,不同的系统实现了component接口,就可以通过这个接口实现用camel标准API进行通信。
这里需要注意的是,MyBatisComponent有一个configurationUri属性,他的默认值为SqlMapConfig.xml,也就是说在默认情况下MyBatisComponent会去加载类路径下的SqlMapConfig.xml去初始化一些配置和用户编写的mapper文件,你当然可以修改这个默认行为,怎么修改呢?通过property注入你的配置文件位置呗。这里需要指出的是,之前我们单独使用mybatis的时候,一种方式是定义一个mapper接口,然后在对应的mapper.xml中将该mapper问价的namespace设置为mapper接口的全路径,这样在运行时,mybatis会利用mapper.xml生成的代理类来作为mapper接口的实现类为程序提供数据访问层的服务。那么我们使用MyBatisComponent来和数据库交互的时候,还要不要定义mapper接口呢?事实证明,我们不再需要定义mapper接口,我们只需要实现mapper.xml即可,那么MyBatisComponent是如何加载到我们所实现的mapper.xml的呢?我们只要在SqlMapConfig.xml中指定我们的mapper.xml文件即可。本实例代码的SqlMapConfig.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> <settings> <setting name="cacheEnabled" value="true" /> </settings> <mappers> <mapper resource="com/ugarden/mapper/UserMapper.xml" /> </mappers> </configuration>
我们再看看UserMapper.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="com.ugarden.repository.UserMapper"> <resultMap id="UserResult" type="com.ugarden.entity.User"> <result property="id" column="id" /> <result property="email" column="email" /> <result property="realName" column="real_name" /> <result property="password" column="password" /> </resultMap> <sql id="columns"> <![CDATA[ id,password,email,show_name,real_name ]]> </sql> <insert id="batchInsertUsers" parameterType="list" useGeneratedKeys="false"> <![CDATA[ INSERT INTO kf_user ( id , password , email , real_name ) VALUES ]]> <foreach collection="list" item="item" separator=","> <![CDATA[ ( #{item.id} , #{item.password} , #{item.email} , #{item.realName} ) ]]> </foreach> </insert> <insert id="insert" parameterType="com.ugarden.entity.User" useGeneratedKeys="false" keyProperty="id"> <![CDATA[ INSERT INTO kf_user ( id , password , email , real_name ) VALUES ( #{id} , #{password} , #{email} , #{realName} ) ]]> </insert> <update id="update" parameterType="com.ugarden.entity.User"> <![CDATA[ UPDATE kf_user SET password = #{password} , email = #{email} , real_name = #{realName} , WHERE id = #{id} ]]> </update> <delete id="deleteUsersRolesById" parameterType="string"> <![CDATA[ DELETE FROM kf_user_role WHERE user_id = #{userId} ]]> </delete> </mapper>
我们在这里也设置了 namespace="com.ugarden.repository.UserMapper",但是我的项目里是没有对应的接口的,这里不设置会不会出问题在写本文的时候还没有试验。写上总是好些,免得让人感到迷茫。
那么我们如何通过camel的API来操作数据库呢?下面是UserServcie.java的内容:
@Service public class UserService { @Autowired private ProducerTemplate producerTemplate; @Autowired private CamelContext camelContext; public void insertUser() throws Exception { //init test user entity User user = new User(); user.setEmail("fanly" + System.currentTimeMillis() + "@126.com"); user.setPassword("123456"); user.setId(String.valueOf(System.currentTimeMillis())); user.setRealName("张双"); Exchange in = this.camelContext.getEndpoint("direct:start").createExchange(ExchangePattern.InOut); in.getIn().setBody(user); Exchange out = this.producerTemplate.send("mybatis:insert?statementType=Insert", in); if (null != out.getException()) { throw out.getException(); } } }
略懂camel的童鞋们立马就明白了,这里首先通过camelContext获取到一个Endpoint,然后获取到输入过程的Exchange对象,由于数据的来源是我们程序提供的,所以endpoint的uri就设置为direct:start。然后将我们要添加的user对象添加到message对象的body中,再将我们的message对象路由到数据库中,我们是通过producerTemplate对象向camelContext对象发出消息的,路由信息附加在了第一个参数中,即"mybatis:insert?statementType=Insert"如果你不懂这个参数的意思,去camel官方看看camel-mybatis的说明就明白了。这句的意思大概就是告诉camelContext对象,我要通过mybatis这个component对象调用一个名叫insert的方法,该方法的statement类型为Insert类型,将Message对象body里的数据插入到数据库。
你可能会问,你这个service里面的producerTemplate,camelContext是哪里来的,为什么你通过一个"mybatis:insert?statementType=Insert"参数,camelContext就知道要去找那个component来进行路由呢?
莫慌,请看spring根配置文件的如下配置内容:
<!-- camel context inti --> <camelContext id="camel" trace="true" xmlns="http://camel.apache.org/schema/spring"> <package>com.ugarden</package> </camelContext>
camelContext就是这样来的,spring一启动的时候他就存在了,如果你有多个camelContext实例的时候,你就要用id来区分注入了。
那producerTemplate是哪里来的呢?刚开始我也纠结这个问题,最后看了一下camelContext的createProducerTemplate的方法注释,发现它是和camelContext一起初始化的,这样就能解释为什么spring能帮我们注入了。
那camel如何知道"mybatis:insert?statementType=Insert"中mybatis指的是哪个呢?您还记得这段配置吗?
<bean id="mybatis" class="org.apache.camel.component.mybatis.MyBatisComponent"> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean>
你看看他的ID,我想如果有多个数据源的时候,我再做如下配置:
<bean id="mybatis1" class="org.apache.camel.component.mybatis.MyBatisComponent"> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean>
那么我是不是可以通过"mybatis1:insert?statementType=Insert"这样来路由呢?是不是试过才知道,写完我就去试一试,camel官方的uri支持范围里是绝对没有mybatis1这种东西的。
现在回到文章开始的问题来,我们如何通过camel来做一个统一的service层呢?很简单,我们只需要将要调用的方法名,body里要路由到数据库的对象,通过参数的方式传递进来不就可以容纳一切变化了吗?从此以后我们只需要通过数据表生成以下mapper.xml,再谢谢特殊的sql就好了。只要是数据库的操作,我们都可以通过camel实现的一个superService类搞定。
最后附上整个例子的源代码供童鞋们下载交流。项目在我的资源栏目里,当然是免积分的了。