搭建基于mybatis的后台框架
发表于6年前(Apr 18, 2016 11:51:17 AM)  阅读 3770  评论 0
标签: mybatis mapper Interceptor 框架搭建
1、前言
最近新搭建一个框架,以前用过mybatis,这次第一次自己搭建基于mybatis的框架,架构基本上沿袭以前hibernate的套路,说到这,笔者对两者的观念基本还是停留在hibernate自动生成SQL,mybatis需要自己写SQL,mybatis自己写SQL开发起来麻烦,但SQL语句写起来灵活,容易实现功能,性能也更容易优化,而hibernate其实大部分情况下性能也够用,优化起来确实需要一定功底,在hibernate有时不容易实现的功能,笔者实际上会结合jdbcTemplate一起使用,所以一直使用hibernate也没发现什么问题。这次选用mybatis,其实主要是一次尝试。
2、概述
所有的实体都必须实现Entity接口;
所有的Mapper都必须继承BaseMapper,BaseMapper是一个泛型类,有两个泛型参数,一个是该Mapper操作的实体类型,另一个是序列化id类型,BaseMapper里面定义了通用的Mapper接口;
所有的DAO接口都继承于IBaseDAO,IBaseDAO里面定义了通用的DAO方法,同样为泛型类;
所有的DAO实现类都实现各自操作实体类型对应的DAO接口,并继承于BaseDAO,同样为泛型类;
3、实现
Entity接口,只定义了获取ID的方法,该ID类型是可以序列化的任何类型:
package com.cangzhitao.common.domain;
import java.io.Serializable;
/**
* Created by cangzhitao on 16/4/13.
*/
public interface Entity {
public Serializable getId();
}
EntityIDLongAuto抽象类,实现Entity接口,所有ID为long,生成策略为Auto的实体继承于它,EntityIDLongAuto的注解都是使用hibernate时设置ID生成策略所使用的,如果使用mybatis,这些注解都可以去掉:
package com.cangzhitao.common.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
* Created by cangzhitao on 16/4/13.
*/
@MappedSuperclass
public abstract class EntityIDLongAuto implements Entity, Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Users,Roles,两个实体类。Entity,Table注解也是hibernate用的,不过这里Table注解不可以去掉,后面DAO会根据这个注解动态获取实体对应的表名,当然您也可以通过别的方式进行配置:
package com.cangzhitao.security.domain;
import com.cangzhitao.common.domain.EntityIDLongAuto;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.util.Date;
/**
* Created by cangzhitao on 16/4/13.
*/
@Entity
@Table(name = "users")
public class Users extends EntityIDLongAuto {
private String username;
private String userpwd;
private String nickname;
private String email;
private String photo;
private String status;
private Date createTime;
private Date lastLoginTime;
private Date lastLoginIp;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUserpwd() {
return userpwd;
}
public void setUserpwd(String userpwd) {
this.userpwd = userpwd;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getLastLoginTime() {
return lastLoginTime;
}
public void setLastLoginTime(Date lastLoginTime) {
this.lastLoginTime = lastLoginTime;
}
public Date getLastLoginIp() {
return lastLoginIp;
}
public void setLastLoginIp(Date lastLoginIp) {
this.lastLoginIp = lastLoginIp;
}
public String getPhoto() {
return photo;
}
public void setPhoto(String photo) {
this.photo = photo;
}
}
package com.cangzhitao.security.domain;
import com.cangzhitao.common.domain.EntityIDLongAuto;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
* Created by cangzhitao on 16/4/14.
*/
@Entity
@Table(name = "roles")
public class Roles extends EntityIDLongAuto {
private String name;
private String desc;
private String photo;
private String status;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getPhoto() {
return photo;
}
public void setPhoto(String photo) {
this.photo = photo;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
BaseMapper,暂时只定义了两个通用方法,一个是根据ID查找对应实体,一个是查找实体集合:
package com.cangzhitao.common.mapper;
import com.cangzhitao.common.domain.Entity;
import org.springframework.stereotype.Repository;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* Created by cangzhitao on 16/4/13.
*/
@Repository
public interface BaseMapper<T extends Entity,ID extends Serializable> {
public T findById(Map<String, Object> map);
public List<T> findList(Map<String, Object> map);
}
对应的BaseMapper.xml,这里使用了动态SQL,需要传入参数__tableName__,为表名,返回类型是Entity接口,并不是实际类型,在后面我们需要动态切换这个返回类型,将它设置成运行时的实际类型:
<?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.cangzhitao.common.mapper.BaseMapper">
<select id="findById" parameterType="Map" resultType="Entity">
SELECT * FROM ${__tableName__} t WHERE t.id = ${id}
</select>
<select id="findList" parameterType="Map" resultType="Entity">
SELECT * FROM ${__tableName__} t
</select>
</mapper>
UsersMapper,RolesMapper,暂时是空实现,里面将来会定义各自特有的方法:
package com.cangzhitao.security.mapper;
import com.cangzhitao.common.mapper.BaseMapper;
import com.cangzhitao.security.domain.Users;
import org.springframework.stereotype.Repository;
/**
* Created by cangzhitao on 16/4/13.
*/
@Repository
public interface UsersMapper extends BaseMapper<Users, Long> {
}
package com.cangzhitao.security.mapper;
import com.cangzhitao.common.mapper.BaseMapper;
import com.cangzhitao.security.domain.Roles;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* Created by cangzhitao on 16/4/13.
*/
@Repository
public interface RolesMapper extends BaseMapper<Roles, Long> {
}
UsersMapper.xml和RolesMapper.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.cangzhitao.security.mapper.UsersMapper">
</mapper>
<?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.cangzhitao.security.mapper.RolesMapper">
</mapper>
IBaseDAO,笔者之前一直在犹豫使用了Mapper,到底还需不要要使用DAO,很多情况下,Mapper就充当了DAO的功能,最终笔者还是在以前的Service-DAO模式的基础下增加了一层Mapper,变成了Service-DAO-Mapper的模式,在这里,Mapper有点像hibernate里面的EntitiyManager,但以前的架构,EntityManager只有一个,而这里每个实体都对应了一个Mapper,至少代码量上更复杂了。IBaseDAO定义了通用的DAO方法,这里注意,这两个方法的参数和BaseMapper里面是不同的:
package com.cangzhitao.common.dao;
import com.cangzhitao.common.domain.Entity;
import com.cangzhitao.common.mapper.BaseMapper;
import com.github.pagehelper.PageInfo;
import java.io.Serializable;
import java.util.List;
/**
* Created by cangzhitao on 16/4/14.
*/
public interface IBaseDAO <T extends Entity,ID extends Serializable> {
public T findById(Serializable id);
public PageInfo<T> findList(int pageNum, int pageSize, String sort);
}
BaseDAO,通用方法的实现主要都在这,每个DAO实现类在初始化时,都会获取对应操作实体的class,类名,以及表名,为BaseMapper.xml需要的参数做好准备:
package com.cangzhitao.common.dao.impl;
import com.cangzhitao.common.dao.IBaseDAO;
import com.cangzhitao.common.domain.Entity;
import com.cangzhitao.common.mapper.BaseMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import javax.persistence.Table;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by cangzhitao on 16/4/14.
*/
@Repository
public class BaseDAO<T extends Entity,ID extends Serializable> implements IBaseDAO<T, ID> {
@Resource
private BaseMapper baseMapper;
private Class<T> entityClass = null;
private String entitySimpleName = "";
private String tableName = "";
public BaseDAO() {
entityClass = getObjectClass();
System.out.println(entityClass.getAnnotations());
Table table = entityClass.getAnnotation(Table.class);
if(table!=null) {
tableName = table.name();
}
entitySimpleName = entityClass.getSimpleName();
}
private Class<T> getObjectClass() {
Type type = getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType)type;
Type[] types = pt.getActualTypeArguments();
return (Class<T>)types[0];
}
return (Class<T>) type;
}
public T findById(Serializable id) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("__tableName__", tableName);
map.put("id", id);
map.put("__entityClass__",entityClass);
T entity = (T) baseMapper.findById(map);
return entity;
}
public PageInfo<T> findList(int pageNum, int pageSize, String sort) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("__tableName__", tableName);
map.put("__entityClass__",entityClass);
PageHelper.startPage(pageNum, pageSize);
List<T> list = baseMapper.findList(map);
PageInfo<T> pageInfo = new PageInfo<T>(list);
return pageInfo;
}
}
IUsersDAO,IRolesDAO:
package com.cangzhitao.security.dao;
import com.cangzhitao.common.dao.IBaseDAO;
import com.cangzhitao.security.domain.Roles;
import com.cangzhitao.security.domain.Users;
import com.github.pagehelper.PageInfo;
import java.util.List;
/**
* Created by cangzhitao on 16/4/14.
*/
public interface IRolesDAO extends IBaseDAO<Roles, Long> {
}
package com.cangzhitao.security.dao;
import com.cangzhitao.common.dao.IBaseDAO;
import com.cangzhitao.security.domain.Users;
/**
* Created by cangzhitao on 16/4/14.
*/
public interface IUsersDAO extends IBaseDAO<Users, Long> {
}
UsersDAO,RolesDAO:
package com.cangzhitao.security.dao.impl;
import com.cangzhitao.common.dao.impl.BaseDAO;
import com.cangzhitao.security.dao.IUsersDAO;
import com.cangzhitao.security.domain.Users;
import com.cangzhitao.security.mapper.UsersMapper;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
/**
* Created by cangzhitao on 16/4/14.
*/
@Repository
public class UsersDAO extends BaseDAO<Users, Long> implements IUsersDAO {
@Resource
private UsersMapper usersMapper;
}
package com.cangzhitao.security.dao.impl;
import com.cangzhitao.common.dao.impl.BaseDAO;
import com.cangzhitao.security.dao.IRolesDAO;
import com.cangzhitao.security.dao.IUsersDAO;
import com.cangzhitao.security.domain.Roles;
import com.cangzhitao.security.domain.Users;
import com.cangzhitao.security.mapper.RolesMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.util.List;
/**
* Created by cangzhitao on 16/4/14.
*/
@Repository
public class RolesDAO extends BaseDAO<Roles, Long> implements IRolesDAO {
@Resource
private RolesMapper rolesMapper;
}
GenericEntityResultSetHandlerInterceptor,该拦截器将在SQL语句执行前,检查mapper.xml里面resultType是不是Entity,如果是Entity,将替换成运行时对应的实体类型,如UsersDAO执行SQL时,将会将Entity替换成Users,RolesDAO执行时又会替换成Roles,这样就不需要再做类型转换了(其实如果不这样做,执行时就会出现Entity不能转换成Users的错误):
package com.cangzhitao.common.interceptor;
import com.cangzhitao.common.domain.Entity;
import com.cangzhitao.common.util.ReflectUtil;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.util.Map;
import java.util.Properties;
/**
* Created by cangzhitao on 16/4/14.
*/
@Intercepts(value = {
@Signature(type = Executor.class, method = "query", args = {
MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class })
})
public class GenericEntityResultSetHandlerInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement stmt = (MappedStatement) invocation.getArgs()[0];
ResultMap resultMap = stmt.getResultMaps().get(0);
Class resultType = resultMap.getType();
if(resultType!=null && resultType == Entity.class) {
Map<String, Object> params = (Map<String, Object>) invocation.getArgs()[1];
ReflectUtil.setFiled(resultMap, "type", params.get("__entityClass__"));
Object o = invocation.proceed();
ReflectUtil.setFiled(resultMap, "type", Entity.class);
return o;
}
return invocation.proceed();
}
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
public void setProperties(Properties properties) {
}
}
mybatis-config.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>
<typeAliases>
<typeAlias type="com.cangzhitao.common.domain.Entity" alias="Entity" />
<typeAlias type="com.cangzhitao.security.domain.Users" alias="Users" />
<typeAlias type="com.cangzhitao.security.domain.Roles" alias="Roles" />
</typeAliases>
<plugins>
<plugin interceptor="com.cangzhitao.common.interceptor.GenericEntityResultSetHandlerInterceptor">
</plugin>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 4.0.0以后版本可以不设置该参数 -->
<property name="dialect" value="mysql"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
<!-- 和startPage中的pageNum效果一样-->
<property name="offsetAsPageNum" value="true"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,使用RowBounds分页会进行count查询 -->
<property name="rowBoundsWithCount" value="true"/>
<!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
<!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
<property name="pageSizeZero" value="true"/>
<!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
<!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
<!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
<property name="reasonable" value="false"/>
<!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
<!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默认值 -->
<!-- 不理解该含义的前提下,不要随便复制该配置 -->
<!--<property name="params" value="pageNum=start;pageSize=limit;"/>-->
<!-- 支持通过Mapper接口参数来传递分页参数 -->
<property name="supportMethodsArguments" value="true"/>
<!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
<property name="returnPageInfo" value="check"/>
</plugin>
</plugins>
<mappers>
<mapper resource="mapper/com/cangzhitao/common/mapper/BaseMapper.xml"/>
<mapper resource="mapper/com/cangzhitao/security/mapper/UsersMapper.xml"/>
<mapper resource="mapper/com/cangzhitao/security/mapper/RolesMapper.xml"/>
</mappers>
</configuration>
applicatonContext.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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-4.1.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd
">
<!-- import jdbc.properties -->
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:db/jdbc.properties</value>
</list>
</property>
</bean>
<!-- config dataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="${jdbc.maxActive}" />
<property name="initialSize" value="${jdbc.initialSize}" />
<property name="maxWait" value="1000"></property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!--<property name="mapperLocations" value="classpath:me/gacl/mapping/*.xml" />-->
<property name="configLocation" value="classpath:db/mybatis-config.xml"></property>
</bean>
<bean id="baseMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.cangzhitao.common.mapper.BaseMapper"></property>
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.cangzhitao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="markerInterface" value="com.cangzhitao.common.mapper.BaseMapper"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<context:component-scan base-package="com.cangzhitao.*">
</context:component-scan>
</beans>
index.jsp,一个简单地测试页面:
<%@ page import="com.cangzhitao.security.dao.IRolesDAO" %>
<%@ page import="java.util.List" %>
<%@ page import="com.cangzhitao.security.dao.IUsersDAO" %>
<%@ page import="com.cangzhitao.security.mapper.UsersMapper" %>
<%@ page import="com.github.pagehelper.PageHelper" %>
<%@ page import="org.springframework.web.context.ContextLoader" %>
<%@ page import="com.cangzhitao.security.domain.Roles" %>
<%@ page import="com.github.pagehelper.PageInfo" %>
<%@ page import="com.cangzhitao.security.domain.Users" %>
<%--
Created by IntelliJ IDEA.
User: cangzhitao
Date: 16/4/13
Time: 下午4:25
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
UsersMapper usersMapper = (UsersMapper) ContextLoader.getCurrentWebApplicationContext().getBean("usersMapper");
IUsersDAO usersDAO = (IUsersDAO) ContextLoader.getCurrentWebApplicationContext().getBean("usersDAO");
IRolesDAO rolesDAO = (IRolesDAO) ContextLoader.getCurrentWebApplicationContext().getBean("rolesDAO");
Users user = usersDAO.findById(1l);
Roles role = rolesDAO.findById(1l);
PageInfo pageInfo = rolesDAO.findList(1,1,"");
%>
<%=user%>
<%=role%>
<%=pageInfo%>
</body>
</html>
4、结束语
这里简单实现了两个通用的查询方法,通过上面的架构,基本上可以使用以前hibernate的套路,感觉被以前熟悉的框架局限住了,这是第一次搭建mybatis的框架,了解得太少,可能mybatis根本不是这么用的,以后慢慢改进。