1. 程式人生 > >springboot通過xml集成mybatis實現mysql數據庫操作【探坑記】

springboot通過xml集成mybatis實現mysql數據庫操作【探坑記】

roc destroy end form表單 account qualifier l數據庫 inpu 資源

這裏主要是介紹在springboot裏面通過xml的方式進行配置,因為xml的配置相對後臺復雜的系統來說,能夠使得系統的配置和邏輯實現分離,避免配置和代碼邏輯過度耦合,xml的配置模式能夠最大限度的實現配置集中,和java代碼邏輯分離。

1. mybatis文件的集成

這裏,通過springboot的@configuration的註解指令,以及@ImportResource註解,很方便的實現mybatis的xml配置文件加載,快速實現mybatis相關信息的配置(含Database)。下面,舉一個簡單的例子,基於mysql實現mybatis的集成。

mybatis和mysql的集成配置(基於durid組件)的xml文件如下(spring-dao.xml):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"  
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop
" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 數據庫的配置信息jdbc.properties在applicationContext.xml當中配置 --> <!--數據源加密操作--> <bean id="myPasswordCallback" class="com.tk.logc.core.mysql.DatabasePasswordCallback" lazy-init="true"/> <!--數據庫連接--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${dataSource.mysql.master-url}" /> <property name="username" value="${dataSource.mysql.master-username}"/> <property name="password" value="${dataSource.mysql.master-password}"/> <!-- 配置初始化大小、最小、最大 --> <property name="initialSize"><value>${dataSource.mysql.initialSize}</value></property> <property name="maxActive"><value>${dataSource.mysql.maxActive}</value></property> <property name="minIdle"><value>${dataSource.mysql.minIdle}</value></property> <!-- 配置獲取連接等待超時的時間 --> <property name="maxWait"><value>${dataSource.mysql.maxWait}</value></property> <!-- 配置監控統計攔截的filters --> <property name="filters"><value>stat</value></property> <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 --> <property name="timeBetweenEvictionRunsMillis"><value>${dataSource.mysql.timeBetweenEvictionRunsMillis}</value></property> <!-- 配置一個連接在池中最小生存的時間,單位是毫秒 --> <property name="minEvictableIdleTimeMillis"><value>${dataSource.mysql.minEvictableIdleTimeMillis}</value></property> <!-- 配置連接中的密碼信息 --> <property name="connectionProperties" value="password=${dataSource.mysql.master-password}"/> <!-- 調用密碼回調,進行解密 --> <property name="passwordCallback" ref="myPasswordCallback"/> <!-- <property name="validationQuery"><value>SELECT x</value></property> <property name="testWhileIdle"><value>true</value></property> <property name="testOnBorrow"><value>false</value></property> <property name="testOnReturn"><value>false</value></property> <property name="poolPreparedStatements"><value>true</value></property> <property name="maxOpenPreparedStatements"><value>20</value></property> --> </bean> <!-- ========================================針對myBatis的配置項============================== --> <!-- 配置sqlSessionFactory --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 掃描com.jersey.dao.mapper這個包以及它的子包下的所有映射接口類 --> <property name="basePackage" value="com.tk.logc.consumer" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean> <!-- 配置掃描器 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 實例化sqlSessionFactory時需要使用上述配置好的數據源以及SQL映射文件 --> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations" value="classpath:mybatis/consumer/*/mapper/*.xml"/> <!-- <property name="mapperLocations" value="classpath:com/tk/logc/consumer/*/dao/mapper/*.xml" /> --> </bean> <!-- ========================================分隔線========================================= --> <!-- 配置Spring的事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 註解方式配置事物 --> <!-- <tx:annotation-driven transaction-manager="transactionManager" /> --> <!-- 攔截器方式配置事物 --> <tx:advice id="transactionAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="append*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="save*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="modify*" propagation="REQUIRED" /> <tx:method name="edit*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="remove*" propagation="REQUIRED" /> <tx:method name="repair" propagation="REQUIRED" /> <tx:method name="delAndRepair" propagation="REQUIRED" /> <tx:method name="get*" propagation="SUPPORTS" /> <tx:method name="find*" propagation="SUPPORTS" /> <tx:method name="load*" propagation="SUPPORTS" /> <tx:method name="search*" propagation="SUPPORTS" /> <tx:method name="datagrid*" propagation="SUPPORTS" /> <tx:method name="*" propagation="SUPPORTS" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="transactionPointcut" expression="execution(* com.tk.logc.consumer.*.service.impl.*.*(..))" /> <aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice" /> </aop:config> <!-- 配置druid監控spring jdbc --> <bean id="druid-stat-interceptor" class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor"> </bean> <bean id="druid-stat-pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut" scope="prototype"> <property name="patterns"> <list> <value>com.tk.logc.consumer.*.service.*</value> </list> </property> </bean> <aop:config> <aop:advisor advice-ref="druid-stat-interceptor" pointcut-ref="druid-stat-pointcut" /> </aop:config> </beans>

在springboot中,需要一個簡單的配置文件實現(不同的配置項,啟用不同的基於@Configuration註解的java配置),非常的簡單。

@Configuration
@ImportResource(locations = {"classpath:config/spring-dao.xml"})
public class MySqlConfig {
}

因為springboot的默認約定大於配置的原則,所以,這個名義上方便了開發者,但是呢,對於不是太熟悉springboot的人來說,可能會是個災難,因為不知道約定的配置都是些啥,不知道配置文件應該放什麽地方,如何去訪問。
默認情況下,springboot的資源性文件,放在resources目錄下,例如靜態資源文件目錄是/static/或者/public/。 當然,這些默認的路徑是可以修改的。 這裏,我們要將spring-dao.xml文件放在resources目錄下的一個子目錄裏,定義為config。目錄層級關系如圖:

技術分享圖片

2.驗證springboot和mybatis集成的效果

按照上圖的結構圖放置mapper對應的xml文件,然後配置一個controller,一個簡單的form表單,做基本測試。

form表單:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Logc User</title>
</head>
<body>
    <div>
        <form id="createUser"  action="${basePath}/account/create" method="post">
            <label>用戶名:</label>
            <input type="text" id="username" name="username">
            <br>
            <label>密碼:</label>
            <input type="password" id="password" name="password">
            <br>
            <label>確認密碼:</label>
            <input type="password" id="vpassword" name="vpassword">
            <br>
            <label>昵稱:</label>
            <input type="text" id="nickname" name="nickname">
            <br>
            <label>圖像:</label>
            <input type="text" id="thumb" name="thumb">
            <br>
            <label>郵箱:</label>
            <input type="text" id="email" name="email">
            <button type="submit" id="doCreate">創建</button>
        </form>
    </div>

    <script src="${basePath}/js/common/jquery-2.1.1.min.js?${softVersion!‘1010101‘}" type="text/javascript"></script>
    <!--
    <script type="text/javascript">
        $(function(){
            $("#doCreate").on("click", function(){
                console.log("click do create button");
                $.ajax({
                    url : "${basePath}/account/create",
                    type : "POST",
                    data : {
                        "username" : $("#username").val(),
                        "password" : $("#password").val(),
                        "vpassword" : $("#vpassword").val(),
                        "nickname" : $("#nickname").val(),
                        "thumb" : $("#thumb").val(),
                        "email" : $("#email").val()
                    },
                    async : false,
                    dataType : "json",
                    success : function(data) {
                      alert("submit successfully");
                    }
                });
            });
        })
    </script>
    -->
</body>
</html>

controller:

    @RequestMapping(value = "/newUser")
    public String newUser() {
        return "/oam/user";
    }

    @RequestMapping(value = "/create", method = RequestMethod.POST)
    @ResponseBody
    public String create(@RequestParam("username")String username,
                         @RequestParam("password")String password,
                         @RequestParam("vpassword")String vpassword,
                         @RequestParam("nickname")String nickname,
                         @RequestParam("thumb")String thumb,
                         @RequestParam("email")String email) {
        System.out.println("username: " + username);
        System.out.println("password: " + password);
        System.out.println("vpassword: " + vpassword);
        System.out.println("nickname: " + nickname);
        System.out.println("thumb: " + thumb);
        System.out.println("email: " + email);
        LogcUser lu = new LogcUser();
        lu.setEmail(email);
        lu.setNickname(nickname);
        lu.setThumb(thumb);
        lu.setUsername(username);
        String salt = PasswordHelper.genSalt();
        lu.setSalt(salt);
        String realPass = PasswordHelper.genPassword(password, salt);
        lu.setPassword(realPass);
        userService.insert(lu);
        return "OK";
    }

按照上面這種配置方式,會出現一個問題

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.tk.logc.consumer.system.dao.LogcUserMapper.insert
    at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:230) ~[mybatis-3.4.2.jar:3.4.2]
    at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:48) ~[mybatis-3.4.2.jar:3.4.2]
    at org.apache.ibatis.binding.MapperProxy.cachedMapperMethod(MapperProxy.java:65) ~[mybatis-3.4.2.jar:3.4.2]
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:58) ~[mybatis-3.4.2.jar:3.4.2]
    at com.sun.proxy.$Proxy79.insert(Unknown Source) ~[na:na]
    at com.tk.logc.consumer.system.service.impl.UserService.insert(UserService.java:60) ~[classes/:na]
    at com.tk.logc.consumer.system.service.impl.UserService$$FastClassBySpringCGLIB$$4c20e181.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:747) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at com.alibaba.druid.support.spring.stat.DruidStatInterceptor.invoke(DruidStatInterceptor.java:72) ~[druid-1.0.18.jar:1.0.18]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294) ~[spring-tx-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    。。。。

這種問題,其實比較常見,但是呢,這些常見多數是在springboot之外的spring框架下比較好處理。例如網上說的這麽幾點都是常見的處理上述問題的方法:
一般的原因是Mapper interface和xml文件的定義對應不上,需要檢查包名,namespace,函數名稱等是否匹配一直,細致的對比,常見錯誤是名稱(全路徑)對不上,不一致
但是呢,我這裏的問題,表面上看,似乎也是Mapper interface的方法和xml裏面的方法對不上。本質上,是springboot限制了mybatis訪問src/main/java下面的xml配置文件,xml類型的配置文件被當做資源文件,必須放在src/main/resources下面,mybatis才可以訪問

調整後,正確的工程文件目錄結構如圖right.jpg, 另外,spring-dao.xml的配置改動如下:

    <!-- 配置sqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--  實例化sqlSessionFactory時需要使用上述配置好的數據源以及SQL映射文件 -->
        <property name="dataSource" ref="dataSource" />        
        <property name="mapperLocations" value="classpath:mybatis/consumer/*/mapper/*.xml"/>        
        <!--
        <property name="mapperLocations" value="classpath:com/tk/logc/consumer/*/dao/mapper/*.xml" />
        -->
    </bean>

上面的紅色部分配置,由<!-- -->註釋掉的配置,就是指定mapper的xml文件在src/main/java路徑下,是錯誤的。 調整後,放到src/main/resources下面的mybatis的主目錄下,就可以正常工作了。調整後的項目文件結構圖如下:

技術分享圖片

這個時候,可以將原來處於src/main/java下面的mapper的xml文件可以完全刪除了。

補充一點(基於javaconfig的方式實現,區別於xml配置模式的代碼)

@Configuration
@MapperScan(basePackages = MySqlConfig.PACKAGE, sqlSessionFactoryRef = "sqlSessionFactory")
public class MySqlConfig {

    static final String PACKAGE = "com.tk.logc.consumer";
    static final String MAPPER_LOCATION = "classpath:mybatis/consumer/*/mapper/*.xml";

    @Value("${dataSource.mysql.master-url}")
    private String url;

    @Value("${dataSource.mysql.master-username}")
    private String user;

    @Value("${dataSource.mysql.master-password}")
    private String password;

    @Value("${dataSource.mysql.driver}")
    private String driverClass;

    @Value("${dataSource.mysql.initialSize}")
    private Integer initialSize;

    @Value("${dataSource.mysql.minIdle}")
    private Integer minIdle;

    @Value("${dataSource.mysql.maxActive}")
    private Integer maxActive;

    @Value("${dataSource.mysql.maxWait}")
    private Integer maxWait;

    @Value("${dataSource.mysql.timeBetweenEvictionRunsMillis}")
    private Integer timeBetweenEvictionRunsMillis;

    @Value("${dataSource.mysql.minEvictableIdleTimeMillis}")
    private Integer minEvictableIdleTimeMillis;

    @Bean(name = "dataSource")
    @Primary
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClass);
        dataSource.setUrl(url);
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        dataSource.setMaxActive(maxActive);
        dataSource.setMinIdle(minIdle);
        dataSource.setInitialSize(initialSize);
        dataSource.setMaxWait(maxWait);
        dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        return dataSource;
    }

    @Bean(name = "transactionManager")
    @Primary
    public DataSourceTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean(name = "sqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MySqlConfig.MAPPER_LOCATION));
        return sessionFactory.getObject();
    }
}

總結:

1. 基於springboot集成mybatis訪問數據庫,mapperLocation配置的路徑必須指向資源文件,不能指向源代碼文件。即mapperLocations必須指向src/main/resources目錄下的某個路徑,不能是src/mian/java路徑下目錄。否則mybatis不能正常運行而出現上述錯誤。

2. 不管是javaconfig還是xml的方式,思路其實是一樣的,相對而言,xml的配置模式看起來更舒服。

springboot通過xml集成mybatis實現mysql數據庫操作【探坑記】