×
文章路径: 框架 > mybatis

搭建基于mybatis的后台框架

发表于2年前(Apr 18, 2016 11:51:17 AM)  阅读 1461  评论 0

分类: Java 框架 mybatis

标签: 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根本不是这么用的,以后慢慢改进。

发表评论