最近公司要做新项目,让我负责搭建项目的主体框架,说实话,之前没做过类似的工作,但接触出ibatis,也有相关封装后的代码,但领导就要求用mybatis,而且还要求必须用注解来搞。是一件很蛋疼的事。以前的开发模式是在xml基础上完成的,做什么都通过配置,是挺容易的,现在的领导要求用注解,也没办法,不能否则注解的开发效率还是挺高的。
但头疼的事发生了,这个框架我该怎么搭建呢,从网上找过很多资料,都没有特别全的,我只能靠我自己了。首先先搭建一个简单的吧。最后我们再来讨论下目前框架的不足和缺点。
新建工程,讲jar包全部导入,当然可以用maven。这里讲解不用这个。
1、从配置spring开始
空的配置文件如下:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
</beans>
我们依次在配置文件配置:数据源,sessionFactory,事务等配置项:
<context:annotation-config />
<context:component-scan base-package="cn.damai"/>
<aop:aspectj-autoproxy proxy-target-class="true" />
<!-- 数据源默认配置 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
<property name="user" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- mybatiss -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:Mybatis_Configuration.xml"/>
</bean>
<!---->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.damai.mplus.*" />
</bean>
<!-- 事物配置开始 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />
<!-- 常见事务设置
propagation:事务传播行为,默认REQUIRED
REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务(最常见选择)
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行
MANDATORY:支持当前事务,如果当前没有事务,就抛出异常
REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER:以非事务方式执行,如果当前存在事务,则抛出异常
isolation:事务隔离级别,默认DEFAULT
read-only:事务是否只读,默认false
-->
2、搭建mybatis
搭建mybatis我们需要两个重要的jar包:
中间有个mybatisext的jar包,这个不是mybatis的必须包,是我自己封装的一个包。至于干什么用的,下面我们来说。
在applicationcontext.xml文件中,我们已经配置了mybatis的内容如下:
<!-- mybatiss -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:Mybatis_Configuration.xml"/>
</bean>
<!---->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.damai.mplus.*" />
</bean>
这两个配置的作用是,sqlSessionFactory 这个都很清楚,就是session工厂,用于操作数据库的工厂类,我们需要将数据源和配置信息告诉他。他帮助我们获取需要的链接,帮助我们对数据库做操作。下面的MapperScannerConfigurer类的作用是:
为了代替手工使用 SqlSessionDaoSupport 或 SqlSessionTemplate 编写数据访问对象 (DAO)的代码,MyBatis-Spring 提供了一个动态代理的实现:MapperFactoryBean。这个类 可以让你直接注入数据映射器接口到你的 service 层 bean 中。当使用映射器时,你仅仅如调 用你的 DAO 一样调用它们就可以了,但是你不需要编写任何 DAO 实现的代码,因为 MyBatis-Spring 将会为你创建代理。
使用注入的映射器代码,在 MyBatis,Spring 或 MyBatis-Spring 上面不会有直接的依赖。 MapperFactoryBean 创建的代理控制开放和关闭 session,翻译任意的异常到 Spring 的 DataAccessException 异常中。此外,如果需要或参与到一个已经存在活动事务中,代理将 会开启一个新的 Spring 事务。
然后,不管我们用ibatis还是mybatis,我们都需要面对的一个问题就是他们对分页的支持问题。这个我们随便百度就能发现,mybatis的分页是基于内存分页的,查找出所有记录再取出偏移量的记录,如果jdbc驱支持absolute定位或者rs.next()到指定偏移位置),其实这样的分页实现基本没用,特别是大量数据情况下。所以说白了,我们需要对mybatis做改造,如何改造,百度上就现成的解决方案,我也是参考的他们的。但其实来讲 想找了现成的也不是很好找,反正是我找到了,我是在这个网站上下载下来经过一些改造后使用的:
https://github.com/miemiedev/mybatis-paginator
改造后,打成了一个mybatis-ext的jar包,放在了classpath下。这个包的作用是能够帮助我们再执行分页查询时对sql进行拦截,真正生成Statement并执行sql的语句是StatementHandler接口的某个实现,这样就可以写个插件对StatementHandler的行为进行拦截,具体原理可以参考网址:
http://www.cnblogs.com/jcli/archive/2011/08/09/2132222.html
现在假设我们的分页插件也搞完了,接下来要做的就是开发我们的baseDao完成对数据库操作的封装,刚说了领导要求我们用注解来做,现在我先用xml来稍微封装下,差不多就行啊,之后再来用注解来搞搞,用xml完成baseDao封装如下:
import java.util.List;
import javax.annotation.Resource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.stereotype.Component;
import cn.damai.mplus.bean.User;
import cn.damai.mplus.framework.common.Page;
import cn.damai.mplus.framework.common.PageBounds;
import cn.damai.mplus.framework.common.PageInfo;
@Component
public class XmlDao<T> {
@Resource
private SqlSessionFactory sessionFactory;
public void setSessionFactory(SqlSessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public void insert(String stateName,T t){
this.sessionFactory.openSession().insert(stateName,t);
}
public void delete(String stateName,T t){
this.sessionFactory.openSession().delete(stateName,t);
}
public void update(String stateName,T t){
this.sessionFactory.openSession().delete(stateName,t);
}
public Page<T> queryForPage(String stateName,T t, PageInfo pageInfo){
int rowCount = (Integer)this.sessionFactory.openSession().selectOne(stateName+"_count");
pageInfo.setRowCount(rowCount);
return new Page(pageInfo,
this.sessionFactory.openSession().
selectList(stateName, t, new PageBounds(pageInfo.getCurrentPage(), pageInfo.getPageSize())));
}
public List<T> queryForParam(String stateName,T param){
return this.sessionFactory.openSession().selectList(stateName, param);
}
public List<T> query(String stateName){
return this.sessionFactory.openSession().selectList(stateName);
}
}
其中我们封装了关于分页的对象Page,在public Page<T> queryForPage(String stateName,T t, PageInfo pageInfo)方法中,我们将pageInfo对象传递过来,这个对象我们只需要提供两个属性值当前页和每页大小即可:根据currentPage和pageSize我就可以计算出offset了。然后再查询结果中,再将rowCount总记录数提供给它,那一共多少页啊等信息也可以计算出来了。
PageInfo pinfo = new PageInfo();
pinfo.setCurrentPage(2);
pinfo.setPageSize(5);
PageInfo这个类的源代码如下:
import java.io.Serializable;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
/**
* 分页器,根据page,limit,totalCount用于页面上分页显示多项内容,计算页码和当前页的偏移量,方便页面分页使用.
*
* @author badqiu
* @author miemiedev
*/
public class PageInfo implements Serializable {
private static final long serialVersionUID = 6089482156906595931L;
private static final int DEFAULT_SLIDERS_COUNT = 7;
private static final int CURR_PAGE = 1;
private static final int PAGE_SIZE = 10;
/**
* 分页大小
*/
public int pageSize = PAGE_SIZE;
/**
* 页数
*/
public int currentPage = CURR_PAGE;
/**
* 起始索引
*/
public int offset = 0;
/**
* 总记录数
*/
public int rowCount;
public PageInfo() {}
public PageInfo(int currentPage, int pageSize, int totalCount) {
super();
this.pageSize = pageSize;
this.rowCount = totalCount;
this.currentPage = computePageNo(currentPage);
}
public PageInfo(HttpServletRequest request){
String currentPage = request.getParameter("currpage");
String pageSize = request.getParameter("pagesize");
if (StringUtils.isNotBlank(currentPage)) {
this.setCurrentPage(Integer.parseInt(currentPage));
} else {
this.setCurrentPage(1);
}
if (StringUtils.isNotBlank(pageSize)) {
this.setPageSize(Integer.parseInt(pageSize));
}
}
/**
* 取得当前页。
*/
/**
* 取得总项数。
*
* @return 总项数
*/
public int getPageSize() {
return pageSize;
}
public int getRowCount() {
return rowCount;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
public void setRowCount(int rowCount) {
this.rowCount = rowCount;
}
/**
* 是否是首页(第一页),第一页页码为1
*
* @return 首页标识
*/
public boolean isFirstPage() {
return currentPage <= 1;
}
/**
* 是否是最后一页
*
* @return 末页标识
*/
public boolean isLastPage() {
return currentPage >= getTotalPages();
}
public int getPrePage() {
if (isHasPrePage()) {
return currentPage - 1;
} else {
return currentPage;
}
}
public int getNextPage() {
if (isHasNextPage()) {
return currentPage + 1;
} else {
return currentPage;
}
}
/**
* 判断指定页码是否被禁止,也就是说指定页码超出了范围或等于当前页码。
*
* @param page 页码
* @return boolean 是否为禁止的页码
*/
public boolean isDisabledPage(int currentPage) {
return ((currentPage < 1) || (currentPage > getTotalPages()) || (currentPage == this.currentPage));
}
/**
* 是否有上一页
*
* @return 上一页标识
*/
public boolean isHasPrePage() {
return (currentPage - 1 >= 1);
}
/**
* 是否有下一页
*
* @return 下一页标识
*/
public boolean isHasNextPage() {
return (currentPage + 1 <= getTotalPages());
}
/**
* 开始行,可以用于oracle分页使用 (1-based)。
*/
public int getStartRow() {
if (getPageSize() <= 0 || rowCount <= 0) return 0;
return currentPage > 0 ? (currentPage - 1) * getPageSize() + 1 : 0;
}
/**
* 结束行,可以用于oracle分页使用 (1-based)。
*/
public int getEndRow() {
return currentPage > 0 ? Math.min(pageSize * currentPage, getRowCount()) : 0;
}
/**
* offset,计数从0开始,可以用于mysql分页使用(0-based)
*/
public int getOffset() {
return currentPage > 0 ? (currentPage - 1) * getPageSize() : 0;
}
/**
* 得到 总页数
*
* @return
*/
public int getTotalPages() {
if (rowCount <= 0) {
return 0;
}
if (pageSize <= 0) {
return 0;
}
int count = rowCount / pageSize;
if (rowCount % pageSize > 0) {
count++;
}
return count;
}
protected int computePageNo(int currentPage) {
return computePageNumber(currentPage, pageSize, rowCount);
}
/**
* 页码滑动窗口,并将当前页尽可能地放在滑动窗口的中间部位。
*
* @return
*/
public Integer[] getSlider() {
return slider(DEFAULT_SLIDERS_COUNT);
}
/**
* 页码滑动窗口,并将当前页尽可能地放在滑动窗口的中间部位。
* 注意:不可以使用 getSlider(1)方法名称,因为在JSP中会与 getSlider()方法冲突,报exception
*
* @return
*/
public Integer[] slider(int slidersCount) {
return generateLinkPageNumbers(getCurrentPage(), (int) getTotalPages(), slidersCount);
}
private static int computeLastPageNumber(int totalItems, int pageSize) {
if (pageSize <= 0) return 1;
int result = (int) (totalItems % pageSize == 0 ?
totalItems / pageSize
: totalItems / pageSize + 1);
if (result <= 1)
result = 1;
return result;
}
private static int computePageNumber(int page, int pageSize, int totalItems) {
if (page <= 1) {
return 1;
}
if (Integer.MAX_VALUE == page
|| page > computeLastPageNumber(totalItems, pageSize)) { //last page
return computeLastPageNumber(totalItems, pageSize);
}
return page;
}
private static Integer[] generateLinkPageNumbers(int currentPageNumber, int lastPageNumber, int count) {
int avg = count / 2;
int startPageNumber = currentPageNumber - avg;
if (startPageNumber <= 0) {
startPageNumber = 1;
}
int endPageNumber = startPageNumber + count - 1;
if (endPageNumber > lastPageNumber) {
endPageNumber = lastPageNumber;
}
if (endPageNumber - startPageNumber < count) {
startPageNumber = endPageNumber - count;
if (startPageNumber <= 0) {
startPageNumber = 1;
}
}
java.util.List<Integer> result = new java.util.ArrayList<Integer>();
for (int i = startPageNumber; i <= endPageNumber; i++) {
result.add(new Integer(i));
}
return result.toArray(new Integer[result.size()]);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("page");
sb.append("{currentPage=").append(currentPage);
sb.append(", pageSize=").append(pageSize);
sb.append(", rowCount=").append(rowCount);
sb.append('}');
return sb.toString();
}
}
好,现在我们用xml的方式将baseDao给搞定了,然后我们再serviceImpl中通过注解的方式可以获取baseDao的实例,就可以来操作这些方法了:
@Service
@Transactional
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
@Autowired
private XmlDao<User> xmlDao;
@Override
public List<User> queryUsers() {
return xmlDao.query("queryAll");
}
@Override
public List<User> queryUsers(User user) {
return xmlDao.queryForParam("queryUsersForParam",user);
}
@Override
public Page<User> queryUsersForPage(User user,PageInfo pageInfo) {
return xmlDao.queryForPage("queryUsersForPage", user, pageInfo);
}
这里我们看到我们将@transaction的注解放在了类上,表示说这个类的所有操作都是有事务的,当然了,具体的配置可以通过属性来配置,这里不讲了。
下面,我们再来看一下用注解怎么开发呢,先不说怎么封装了,现在你让我用注解的方式完成crud,我也不怎么熟悉,所以咱先看如何用注解完成最简单的crud:
import java.util.List;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import cn.damai.mplus.bean.User;
import cn.damai.mplus.framework.base.AnnotationDao;
import cn.damai.mplus.framework.common.PageInfo;
public interface UserDao extends AnnotationDao<User>{
@Insert(value="insert into d_user values (#{username},#{password})")
public void addUser(User user);
@Select(value="select * from d_user where username=#{username}")
public List<User> getUser(User user);
@Select(value="select * from d_user limit #{offset},#{pageSize}")
public List<User> getUsersByPage(PageInfo pageInfo);
}
很简单,所以我就不过多的解释了,insert代表插入,select是查询。value是sql,除此之外,我们还可以配置@result等注解,这些可以自行了解一下。
现在问题是如果我们不自己用注解开发,而是用注解的思想完成一个baseMaper我们应该怎么做呢,我也没什么思路,但我借鉴了这个网站并用了一下感觉还可以:
http://blog.csdn.net/beiouwolf/article/details/7347797
缺点是没有提供通用查询的方法。本来想自己搞搞看看能不能写出一个来,搞来搞去也没成功。唉。希望高人指路啊。
所以,暂时也不管了,现在我们的baseMapper(注解)和baseDao(xml)假设都搞定了,spring也ok了,就剩下struts2了,这个也很简单了:
import java.util.List;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.ExceptionMapping;
import org.apache.struts2.convention.annotation.ExceptionMappings;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.ParentPackage;
import org.apache.struts2.convention.annotation.Results;
import org.apache.struts2.convention.annotation.Result;
import org.springframework.beans.factory.annotation.Autowired;
import cn.damai.mplus.bean.User;
import cn.damai.mplus.framework.base.BaseAction;
import cn.damai.mplus.framework.common.Page;
import cn.damai.mplus.framework.common.PageInfo;
import cn.damai.mplus.service.impl.UserServiceImpl;
@ParentPackage("struts-default")
@Namespace("/user")
@Results({
@Result(name = "success", location = "/index.jsp"),
@Result(name = "error", location = "/error.jsp")
})
@ExceptionMappings({
@ExceptionMapping(exception = "java.lang.RuntimeException", result = "error")
})
public class UserAction extends BaseAction{
private static final long serialVersionUID = 1L;
@Autowired
private UserServiceImpl userService;
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Action(value = "add", results = { @Result(name = "success", location = "/success1.jsp") })
public String add(){
this.userService.addUser(user);
List<User> users = this.userService.queryUsers();
this.setRequestAttribute("users", users);
// Map request = (Map)ActionContext.getContext().get("request");
// request.put("users", users);
return "success";
}
配置文件也没用,直接用注解就搞定了,当然你用注解,得导入相关的jar包:
这里也提供一下用xml方式配置struts2的样式:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="user" extends="damai-default">
<action name="user_*" class="userAction" method="{1}">
<result name="success">/success.jsp</result>
</action>
</package>
</struts>
这样,我们的ss-mybatis的框架就搭建完成了,随便测试下
ok!!!