主要是MyBatis與SpringBoot的繼承,此處與SpringCloud無關,做爲記錄。前端
數據庫使用的是MySql,因此須要加入如下的依賴:java
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
配置文件application.yml以下:mysql
eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ server: port: 7001 spring: application: name: SERVICE-USER datasource: #數據源 driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/owl-bookstore?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false username: root password: root dbcp2: #dbcp2鏈接池 max-idle: 10 mybatis: #實體類的包 type-aliases-package: cn.net.bysoft.owl.bookstore.facade.user.entity #mapper.xml存放的路徑 mapperLocations: classpath:mapper/*.xml #mybatis配置文件存放的路徑 configLocation: classpath:mybatis-config.xml
經過上面的依賴和配置,基本能夠用本身熟悉的方式使用Mybatis了。git
下面來記錄一下我本身的實現,實現很簡單,沒有考慮過多的東西。github
能夠採用註解的方式使用mybatis,可是此處我使用的是比較原始的方法,配置mapper來使用mybatis。spring
直接貼出代碼,BaseEntity類:sql
package cn.net.bysoft.owl.bookstore.common.entity; import java.io.Serializable; public abstract class BaseEntity implements Serializable { private static final long serialVersionUID = -1105239108084459358L; private Long id; public Long getId() { return id; } public void setId(Long id) { this.id = id; } }
分頁的實體類:數據庫
package cn.net.bysoft.owl.bookstore.common.entity; import java.io.Serializable; import java.util.List; /** * 分頁組件。<br> * 用於前端DataGrid控件初始化。<br> * 組件內包含當前頁、每頁顯示條數、總記錄數、數據、總頁數、頁碼索引等信息。 * * @author XuePeng */ public class PageBean<T> implements Serializable { private static final long serialVersionUID = 8470697978259453214L; private int currentPage; private int numPerPage; private int totalCount; private List<T> recordList; private int pageCount; private int beginPageIndex; private int endPageIndex; public PageBean(int currentPage, int numPerPage, int totalCount, List<T> recordList) { this.currentPage = currentPage; this.numPerPage = numPerPage; this.totalCount = totalCount; this.recordList = recordList; // 計算總頁碼 pageCount = (totalCount + numPerPage - 1) / numPerPage; // 計算 beginPageIndex 和 endPageIndex // 總頁數很少於10頁,則所有顯示 if (pageCount <= 10) { beginPageIndex = 1; endPageIndex = pageCount; } // 總頁數多於10頁,則顯示當前頁附近的共10個頁碼 else { // 當前頁附近的共10個頁碼(前4個 + 當前頁 + 後5個) beginPageIndex = currentPage - 4; endPageIndex = currentPage + 5; // 當前面的頁碼不足4個時,則顯示前10個頁碼 if (beginPageIndex < 1) { beginPageIndex = 1; endPageIndex = 10; } // 當後面的頁碼不足5個時,則顯示後10個頁碼 if (endPageIndex > pageCount) { endPageIndex = pageCount; beginPageIndex = pageCount - 10 + 1; } } } public int getCurrentPage() { return currentPage; } public void setCurrentPage(int currentPage) { this.currentPage = currentPage; } public int getNumPerPage() { return numPerPage; } public void setNumPerPage(int numPerPage) { this.numPerPage = numPerPage; } /** * @return 得到總記錄數。 */ public int getTotalCount() { return totalCount; } public void setTotalCount(int totalCount) { this.totalCount = totalCount; } public List<T> getRecordList() { return recordList; } public void setRecordList(List<T> recordList) { this.recordList = recordList; } public int getPageCount() { return pageCount; } public void setPageCount(int pageCount) { this.pageCount = pageCount; } public int getBeginPageIndex() { return beginPageIndex; } public void setBeginPageIndex(int beginPageIndex) { this.beginPageIndex = beginPageIndex; } public int getEndPageIndex() { return endPageIndex; } public void setEndPageIndex(int endPageIndex) { this.endPageIndex = endPageIndex; } }
package cn.net.bysoft.owl.bookstore.common.entity; import java.io.Serializable; /** * 分頁參數的實體類。<br> * 設置對象的當前頁數和每頁記錄數,進行數據庫分頁查詢。 * * @author XuePeng */ public class PageParam implements Serializable { private static final long serialVersionUID = 6297178964005032338L; private int pageNum; private int numPerPage; public PageParam(int pageNum, int numPerPage) { this.pageNum = pageNum; this.numPerPage = numPerPage; } public int getPageNum() { return pageNum; } public void setPageNum(int pageNum) { this.pageNum = pageNum; } public int getNumPerPage() { return numPerPage; } public void setNumPerPage(int numPerPage) { this.numPerPage = numPerPage; } }
接下來,BaseDao接口,定義了CURD的基本方法,和經過分頁查詢的方法,具體以下:apache
package cn.net.bysoft.owl.bookstore.common.core.dao; import java.util.List; import java.util.Map; import cn.net.bysoft.owl.bookstore.common.entity.BaseEntity; import cn.net.bysoft.owl.bookstore.common.entity.PageBean; import cn.net.bysoft.owl.bookstore.common.entity.PageParam; public interface BaseDao<T extends BaseEntity> { long insert(T entity); int update(T entity); int deleteById(long id); int deleteByIds(long... id); T findById(long id); T findByParam(Map<String, Object> paramMap); List<T> listByParam(Map<String, Object> paramMap); PageBean<T> listByPageAndParam(PageParam pageParam, Map<String, Object> paramMap); PageBean<T> listByPageAndParam(PageParam pageParam, Map<String, Object> paramMap, String sqlId); long countByParam(Map<String, Object> paramMap); }
BaseDaoImpl類,實現了BaseDao接口:緩存
package cn.net.bysoft.owl.bookstore.common.core.dao; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.support.SqlSessionDaoSupport; import org.springframework.beans.factory.annotation.Autowired; import cn.net.bysoft.owl.bookstore.common.entity.BaseEntity; import cn.net.bysoft.owl.bookstore.common.entity.PageBean; import cn.net.bysoft.owl.bookstore.common.entity.PageParam; public abstract class BaseDaoImpl<T extends BaseEntity> extends SqlSessionDaoSupport implements BaseDao<T> { private static final String SQL_INSERT = "insert"; private static final String SQL_UPDATE = "update"; private static final String SQL_DELETE_BY_ID = "deleteById"; private static final String SQL_DELETE_BY_IDS = "deleteByIds"; private static final String SQL_FIND_BY_ID = "findById"; private static final String SQL_FIND_BY_PARAM = "findByParam"; private static final String SQL_LIST_BY_PARAM = "listByParam"; private static final String SQL_LIST_BY_PAGE = "listByPageAndParam"; private static final String SQL_COUNT_BY_PARAM = "countByParam"; // SqlSessionTemplate實例(要求Spring中進行SqlSessionTemplate的配置) // 能夠調用sessionTemplate完成數據庫操做 private SqlSessionTemplate sessionTemplate; // SqlSessionFactory實例(要求Spring中進行SqlSessionFactory的配置) // 能夠調用sessionFactory打開一個SqlSession private SqlSessionFactory sessionFactory; public SqlSessionTemplate getSessionTemplate() { return this.sessionTemplate; } @Autowired public void setSessionTemplate(SqlSessionTemplate sessionTemplate) { super.setSqlSessionTemplate(sessionTemplate); this.sessionTemplate = sessionTemplate; } public SqlSessionFactory getSessionFactory() { return this.sessionFactory; } @Override @Autowired public void setSqlSessionFactory(SqlSessionFactory sessionFactory) { super.setSqlSessionFactory(sessionFactory); this.sessionFactory = sessionFactory; } @Override public long insert(T entity) { // 對數據庫進行insert操做。 // 執行Mapper配置文件中的insert方法。 int result = sessionTemplate.insert(getStatement(SQL_INSERT), entity); // 判斷若是要新建的實體對象不爲null,而且成功保存到數據庫了,則返回其主鍵。 if (entity != null && entity.getId() != null && result > 0) { return entity.getId(); } return result; } @Override public int update(T entity) { // 對數據庫進行update操做,並返回影響行數。 // 執行Mapper配置文件中的update方法。 return sessionTemplate.update(getStatement(SQL_UPDATE), entity); } @Override public int deleteById(long id) { // 對數據庫進行刪除操做。 // 執行Mapper配置文件中的deleteById方法。 return sessionTemplate.delete(getStatement(SQL_DELETE_BY_ID), id); } @Override public int deleteByIds(long... ids) { return sessionTemplate.delete(getStatement(SQL_DELETE_BY_IDS), ids); } @Override public T findById(long id) { // 對數據庫進行查詢操做。 // 執行Mapper配置文件中的findById方法。 return sessionTemplate.selectOne(getStatement(SQL_FIND_BY_ID), id); } @Override public T findByParam(Map<String, Object> param) { // 對數據庫進行查詢操做。 // 執行Mapper配置文件中的findByParam方法。 if (param == null || param.isEmpty()) { return null; } return sessionTemplate.selectOne(getStatement(SQL_FIND_BY_PARAM), param); } @Override public List<T> listByParam(Map<String, Object> param) { // 對數據庫進行查詢操做。 // 執行Mapper配置文件中的findListByParam方法。 return sessionTemplate.selectList(getStatement(SQL_LIST_BY_PARAM), param); } @Override public PageBean<T> listByPageAndParam(PageParam pageParam, Map<String, Object> param) { // 對數據庫進行查詢操做。 // 執行Mapper配置文件中的findListByPageAndParam方法。 HashMap<String, Object> params; if (param == null) { params = new HashMap<>(); } else { params = (HashMap<String, Object>) param; } // 獲取分頁數據集 , 注切勿換成 sessionTemplate 對象。 // 使用RowBounds進行分頁。 List<T> list = getSqlSession().selectList(getStatement(SQL_LIST_BY_PAGE), params, new RowBounds((pageParam.getPageNum() - 1) * pageParam.getNumPerPage(), pageParam.getNumPerPage())); // 統計總記錄數 Object countObject = (Object) getSqlSession().selectOne(getStatement(SQL_LIST_BY_PAGE), new ExecutorInterceptor.CountParameter(params)); Long count = Long.valueOf(countObject.toString()); return new PageBean<>(pageParam.getPageNum(), pageParam.getNumPerPage(), count.intValue(), list); } @Override public PageBean<T> listByPageAndParam(PageParam pageParam, Map<String, Object> param, String sqlId) { // 對數據庫進行查詢操做。 // 執行Mapper配置文件中的自定義的方法。 HashMap<String, Object> params; if (param == null) { params = new HashMap<>(); } else { params = (HashMap<String, Object>) param; } // 獲取分頁數據集 , 注切勿換成 sessionTemplate 對象 List<T> list = getSqlSession().selectList(getStatement(sqlId), params, new RowBounds((pageParam.getPageNum() - 1) * pageParam.getNumPerPage(), pageParam.getNumPerPage())); // 統計總記錄數 Object countObject = (Object) getSqlSession().selectOne(getStatement(sqlId), new ExecutorInterceptor.CountParameter(params)); Long count = Long.valueOf(countObject.toString()); return new PageBean<>(pageParam.getPageNum(), pageParam.getNumPerPage(), count.intValue(), list); } @Override public long countByParam(Map<String, Object> param) { // 對數據庫進行查詢操做。 // 執行Mapper配置文件中的findCountByParam方法。 return sessionTemplate.selectOne(getStatement(SQL_COUNT_BY_PARAM), param); } protected String getStatement(String sqlId) { String name = this.getClass().getName(); StringBuilder sb = new StringBuilder(); sb.append(name).append(".").append(sqlId); return sb.toString(); } }
用於分頁的攔截器類:
package cn.net.bysoft.owl.bookstore.common.core.dao; import java.util.ArrayList; import java.util.List; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.MappedStatement.Builder; import org.apache.ibatis.mapping.ResultMap; import org.apache.ibatis.mapping.ResultMapping; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.plugin.Interceptor; public abstract class AbstractInterceptor implements Interceptor { protected MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource, boolean isCount) { Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType()); builder.resource(ms.getResource()); builder.fetchSize(ms.getFetchSize()); builder.statementType(ms.getStatementType()); builder.keyGenerator(ms.getKeyGenerator()); String[] s = ms.getKeyProperties(); if (s == null) { builder.keyProperty(null); } else { builder.keyProperty(s[0]); } builder.timeout(ms.getTimeout()); builder.parameterMap(ms.getParameterMap()); if (isCount) { List<ResultMap> resultMaps = new ArrayList<>(); resultMaps.add(new ResultMap.Builder(ms.getConfiguration(), ms.getId(), Integer.class, new ArrayList<ResultMapping>()).build()); builder.resultMaps(resultMaps); } else { builder.resultMaps(ms.getResultMaps()); } builder.cache(ms.getCache()); return builder.build(); } public static class BoundSqlSqlSource implements SqlSource { BoundSql boundSql; public BoundSqlSqlSource(BoundSql boundSql) { this.boundSql = boundSql; } @Override public BoundSql getBoundSql(Object parameterObject) { return boundSql; } } }
package cn.net.bysoft.owl.bookstore.common.core.dao; public interface Dialect { default boolean supportsLimit() { return false; } default boolean supportsLimitOffset() { return supportsLimit(); } default String getLimitString(String sql, int offset, int limit) { return getLimitString(sql, offset, Integer.toString(offset), limit, Integer.toString(limit)); } String getLimitString(String sql, int offset, String offsetPlaceholder, int limit, String limitPlaceholder); }
package cn.net.bysoft.owl.bookstore.common.core.dao; import java.util.Properties; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.apache.log4j.Logger; @Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) }) public class ExecutorInterceptor extends AbstractInterceptor { private static final Logger LOGGER = Logger.getLogger(ExecutorInterceptor.class); private static final int MAPPED_STATEMENT_INDEX = 0; private static final int PARAMETER_INDEX = 1; private static final int ROWBOUNDS_INDEX = 2; private static final String ORDER_BY = "order by"; private Dialect dialect; @Override public Object intercept(Invocation invocation) throws Throwable { processIntercept(invocation.getArgs()); return invocation.proceed(); } private void processIntercept(final Object[] queryArgs) { MappedStatement ms = (MappedStatement) queryArgs[MAPPED_STATEMENT_INDEX]; Object parameter = queryArgs[PARAMETER_INDEX]; final RowBounds rowBounds = (RowBounds) queryArgs[ROWBOUNDS_INDEX]; int offset = rowBounds.getOffset(); int limit = rowBounds.getLimit(); // 分頁 if (dialect.supportsLimit() && (offset != RowBounds.NO_ROW_OFFSET || limit != RowBounds.NO_ROW_LIMIT)) { BoundSql boundSql = ms.getBoundSql(parameter); String sql = boundSql.getSql().replaceAll("\\s{2,}", " ").trim(); if (dialect.supportsLimitOffset()) { sql = dialect.getLimitString(sql, offset, limit); offset = RowBounds.NO_ROW_OFFSET; } else { sql = dialect.getLimitString(sql, 0, limit); } limit = RowBounds.NO_ROW_LIMIT; queryArgs[ROWBOUNDS_INDEX] = new RowBounds(offset, limit); BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject()); MappedStatement newMs = copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql), false); queryArgs[MAPPED_STATEMENT_INDEX] = newMs; LOGGER.debug("==>" + sql); } else if (parameter instanceof CountParameter) { // 獲取總數 parameter = ((CountParameter) parameter).getParameter(); BoundSql boundSql = ms.getBoundSql(parameter); String sql = boundSql.getSql().replaceAll("\\s{2,}", " ").replace(" FROM", " from") .replace("ORDER BY", ORDER_BY).replace("GROUP BY", ORDER_BY).trim(); if (sql.split("from").length > 2 || sql.split(ORDER_BY).length > 2 || sql.indexOf(ORDER_BY) > -1) { sql = "select count(1) from (" + sql + ") tmp"; } else { int fromIndex = sql.indexOf(" from"); sql = "select count(1)" + sql.substring(fromIndex); int orderByIndex = sql.indexOf(ORDER_BY); if (orderByIndex > 0) { sql = sql.substring(0, orderByIndex); } } BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject()); MappedStatement newMs = copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql), true); queryArgs[MAPPED_STATEMENT_INDEX] = newMs; queryArgs[PARAMETER_INDEX] = parameter; LOGGER.debug("==>" + sql); } } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { String dialectClass = properties.getProperty("dialectClass"); try { dialect = (Dialect) Class.forName(dialectClass).newInstance(); } catch (Exception e) { throw new RuntimeException("方言加載屬性異常"); } } public static class CountParameter { private Object parameter; public CountParameter(Object parameter) { this.parameter = parameter; } public Object getParameter() { return parameter; } } }
package cn.net.bysoft.owl.bookstore.common.core.dao; public class MySqlDialect implements Dialect { @Override public String getLimitString(String sql, int offset, String offsetPlaceholder, int limit, String limitPlaceholder) { StringBuilder stringBuilder = new StringBuilder(sql); stringBuilder.append(" limit "); if (offset > 0) { stringBuilder.append(offsetPlaceholder); stringBuilder.append(","); } stringBuilder.append(limitPlaceholder); return stringBuilder.toString(); } }
最後是Mapper和Mybatis.Config配置文件:
<?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="cn.net.bysoft.owl.bookstore.service.user.dao.impl.UserDaoImpl"> <sql id="condition_sql"> <if test="email != null and email != ''"> email = #{email} </if> </sql> <resultMap id="userMap" type="User"> <id column="id" property="id" /> <result property="email" column="email" /> <result property="mobile" column="mobile" /> <result property="password" column="pwd" /> </resultMap> <insert id="insert" parameterType="User" keyProperty="id" useGeneratedKeys="true"> INSERT INTO obs_user (email, mobile, pwd) VALUES (#{email}, #{mobile}, #{password}) </insert> <select id="findById" parameterType="Long" resultMap="userMap"> SELECT id, email, mobile FROM obs_user <where> id = #{id} </where> </select> <select id="countByParam" parameterType="java.util.Map" resultType="Long"> SELECT COUNT(0) FROM obs_user <where> <include refid="condition_sql" /> </where> </select> </mapper>
<?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" /> <!-- 全局啓用或禁用延遲加載。當禁用時, 全部關聯對象都會即時加載 --> <setting name="lazyLoadingEnabled" value="false" /> <!-- 容許或不容許多種結果集從一個單獨 的語句中返回(須要適合的驅動) --> <setting name="multipleResultSetsEnabled" value="true" /> <!-- 使用列標籤代替列名。 不一樣的驅動在這 方便表現不一樣。 參考驅動文檔或充分測 試兩種方法來決定所使用的驅動 --> <setting name="useColumnLabel" value="true" /> <!-- 容許 JDBC 支持生成的鍵。 須要適合的 驅動。 若是設置爲 true 則這個設置強制 生成的鍵被使用, 儘管一些驅動拒絕兼 容但仍然有效(好比 Derby) --> <setting name="useGeneratedKeys" value="false" /> <!-- 配置默認的執行器。SIMPLE 執行器沒 有什麼特別之處。REUSE 執行器重用 預處理語句。BATCH 執行器重用語句 和批量更新 --> <setting name="defaultExecutorType" value="SIMPLE" /> <!-- 設置超時時間, 它決定驅動等待一個數 據庫響應的時間 --> <setting name="defaultStatementTimeout" value="100" /> <setting name="safeRowBoundsEnabled" value="false" /> <setting name="mapUnderscoreToCamelCase" value="false" /> <setting name="localCacheScope" value="SESSION" /> <setting name="jdbcTypeForNull" value="OTHER" /> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString" /> <!-- 打印查詢語句 --> <setting name="logImpl" value="STDOUT_LOGGING" /> </settings> </configuration>