主要是MyBatis与SpringBoot的继承,此处与SpringCloud无关,作为记录。
依赖与配置
数据库使用的是MySql,所以需要加入以下的依赖:
<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如下:
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了。
下面来记录一下我自己的实现,实现很简单,没有考虑过多的东西。
可以采用注解的方式使用mybatis,但是此处我使用的是比较原始的方法,配置mapper来使用mybatis。
- 定义一个BaseDao接口和BaseDaoImpl实现类,编写一些通用的Dao方法。
- 定义一个BaseEntity实体类,同样定义一些常用的方法。
- 分页采用了Mybatis的拦截器实现。
直接贴出代码,BaseEntity类:
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的基本方法,和通过分页查询的方法,具体如下:
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>
Github
https://github.com/XuePeng87/owl-bookstore