一、简单例子
说明:本例基于mybatis-3.2.6。
1.新建web项目mybatis,引入jar包mybatis-3.2.6.jar。
2.新建java类com.hanjun.entity.User(数据库中建立与些类对应的t_user表)
private int id;
private String userName;
private String userAge;
private String userAddress;
3.在User类同包下新建User.xml
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
select * from t_user where id = #{id}
说明:namespace为自定义的包名,包名.select的id确定唯一sql。parameterType可以不要。
4.在src下新建mybatis配置文件Configuration.xml
"http://mybatis.org/dtd/mybatis-3-config.dtd">
说明:User.xml中的resultType对应此处的alias。如果不声明alias,修改User.xml中的resultType为com.hanjun.entity.User也可
5.测试:
public static void main(String[] args) {
Reader reader = null;
try {
reader = Resources.getResourceAsReader("Configuration.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlSessionFactory.openSession();
try {
User user = (User) session.selectOne("com.hanjun.entity.User.selectUserByID", 1);
System.out.println(user.getUserAddress());
System.out.println(user.getUserName());
} finally {
session.close();
}
}
6.优化,新建com.hanjun.dao.IUserDao,并修改User.xml中mapper的namespace为com.hanjun.dao.IUserDao
public interface IUserDao {
// 方法名与User.xml中select的id保持一致
public User selectUserByID(int id);
}
修改测试方法,可以直接使用IUserDao调用数据库了
SqlSession session = sqlSessionFactory.openSession();
IUserDao userDao = session.getMapper(IUserDao.class);
try {
User user = userDao.selectUserByID(1);
System.out.println(user.getUserAddress());
System.out.println(user.getUserName());
} finally {
session.close();
}
二、增删改查
1.增加数据。User.xml中增加
insert into t_user(id,userName,userAge,userAddress)
values(q_user.nextval,#{userName},#{userAge},#{userAddress})
说明:如果是MySql等自增主键,insert的属性 seGeneratedKeys设置为"true"表明要MyBatis获取由数据库自动生成的主键;keyProperty="id"指定把获取到的主键值注入到User的id属性
IUserDao中增加
public void addUser(User user);
测试
User user = new User();
user.setUserAge("26");
user.setUserName("luowei");
user.setUserAddress("湖南");
userDao.addUser(user);
session.commit();
说明:增删改操作必须执行session.commit()方法。
2.修改数据。User.xml中增加
update t_user set userName=#{userName},userAge=#{userAge},userAddress=#{userAddress} where id=#{id}
IUserDao中增加
public void updateUser(User user);
测试
User user = userDao.selectUserByID(3);
user.setUserAge("27");
userDao.updateUser(user);
3.删除数据。User.xml中增加
delete from t_user where id=#{id}
IUserDao中增加
public void deleteUser(User user);
测试
User user = userDao.selectUserByID(3);
user.setUserAge("27");
userDao.deleteUser(user);
4.List查询。User.xml中增加
IUserDao中增加
public List
测试
List
for(User user:users){
System.out.println(user.getId()+":"+user.getUserName()+":"+user.getUserAddress());
}
5.多对一查询。新建表t_article和实体类com.hanjun.entity.Article
private int id;
private User user;
private String title;
private String content;
新建com.hanjun.entity.IArticleDao
public interface IArticleDao {
public List findArticles(Article article);
}
修改Configuration.xml
新建Article.xml
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
说明:autoMapping设置为true,sql列名和实体类属性名一致时自动注入,可省略配置一些association
说明:resultMapArticle引用resultMapUser也可写为:
测试
IArticleDao articleDao = session.getMapper(IArticleDao.class);
User user = new User();
user.setId(4);
Article queryVO = new Article();
queryVO.setUser(user);
List articles = articleDao.findArticles(queryVO);
for(Article article : articles){
System.out.println(article.getTitle()+":"+article.getUser().getUserName());
}
注:实验结果articles应该返回多条数据时只返回一条,删除association配置就返回正常,换成mybatis-3.1.1.jar后问题解决。后来又换回mybatis-3.2.6,经测试article的id在sql中的别名不能是id,改为aid后问题解决。
6.SQL打印
添加commons-logging-1.1.1.jar log4j-1.2.17.jar src下新建log4j.properties
log4j.logger.com.hanjun.dao=DEBUG,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
log4j.appender.Console.Threshold=DEBUG
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%-5p] [%d] %t %c - %m%n
三、与spring整合
1.加入jar包
commons-dbcp.jar
commons-logging-1.1.1.jar
commons-pool.jar
mybatis-spring-1.2.2.jar
spring-aop-3.2.8.RELEASE.jar
spring-beans-3.2.8.RELEASE.jar
spring-context-3.2.8.RELEASE.jar
spring-core-3.2.8.RELEASE.jar
spring-expression-3.2.8.RELEASE.jar
spring-jdbc-3.2.8.RELEASE.jar
spring-tx-3.2.8.RELEASE.jar
2.src下新建applicationContext.xml
xsi:schemaLocation="Index of /schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
优化:sqlSessionFactory增加
return (List
}
public void setResults(List> results) {
this.results = results;
}
}
3.新建com.hanjun.intercept.PageInterceptor.java
@Intercepts( { @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
public class PageInterceptor implements Interceptor {
private String databaseType;// 数据库类型,不同的数据库有不同的分页方法
public Object intercept(Invocation invocation) throws Throwable {
// 对于StatementHandler其实只有两个实现类,一个是RoutingStatementHandler,另一个是抽象类baseStatementHandler,
// baseStatementHandler有三个子类,分别是SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler,
// SimpleStatementHandler是用于处理Statement的,PreparedStatementHandler是处理PreparedStatement的,而CallableStatementHandler是
// 处理CallableStatement的。Mybatis在进行Sql语句处理的时候都是建立的RoutingStatementHandler,而在RoutingStatementHandler里面拥有一个
// StatementHandler类型的delegate属性,RoutingStatementHandler会依据Statement的不同建立对应的baseStatementHandler,即SimpleStatementHandler、
// PreparedStatementHandler或CallableStatementHandler,在RoutingStatementHandler里面所有StatementHandler接口方法的实现都是调用的delegate对应的方法。
// 我们在PageInterceptor类上已经用@Signature标记了该Interceptor只拦截StatementHandler接口的prepare方法,又因为Mybatis只有在建立RoutingStatementHandler的时候
// 是通过Interceptor的plugin方法进行包裹的,所以我们这里拦截到的目标对象肯定是RoutingStatementHandler对象。
RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
// 通过反射获取到当前RoutingStatementHandler对象的delegate属性
StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate");
// 获取到当前StatementHandler的
// boundSql,这里不管是调用handler.getBoundSql()还是直接调用delegate.getBoundSql()结果是一样的,因为之前已经说过了
// RoutingStatementHandler实现的所有StatementHandler接口方法里面都是调用的delegate对应的方法。
BoundSql boundSql = delegate.getBoundSql();
// 拿到当前绑定Sql的参数对象,就是我们在调用对应的Mapper映射语句时所传入的参数对象
Object obj = boundSql.getParameterObject();
// 这里我们简单的通过传入的是Page对象就认定它是需要进行分页操作的。
if (obj instanceof Page) {
Page page = (Page) obj;
// 通过反射获取delegate父类baseStatementHandler的mappedStatement属性
MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");
// 拦截到的prepare方法参数是一个Connection对象
Connection connection = (Connection) invocation.getArgs()[0];
// 获取当前要执行的Sql语句,也就是我们直接在Mapper映射语句中写的Sql语句
String sql = boundSql.getSql();
// 给当前的page参数对象设置总记录数
this.setTotalRecord(page, mappedStatement, connection);
// 获取分页Sql语句
String pageSql = this.getPageSql(page, sql);
// 利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
}
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
this.databaseType = properties.getProperty("databaseType");
}
private String getPageSql(Page page, String sql) {
StringBuffer sqlBuffer = new StringBuffer(sql);
if ("mysql".equalsIgnoreCase(databaseType)) {
return getMysqlPageSql(page, sqlBuffer);
} else if ("oracle".equalsIgnoreCase(databaseType)) {
return getOraclePageSql(page, sqlBuffer);
}
return sqlBuffer.toString();
}
private String getMysqlPageSql(Page page, StringBuffer sqlBuffer) {
// 计算第一条记录的位置,Mysql中记录的位置是从0开始的。
int offset = (page.getPageNo() - 1) * page.getPageSize();
sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize());
return sqlBuffer.toString();
}
private String getOraclePageSql(Page page, StringBuffer sqlBuffer) {
// 计算第一条记录的位置,Oracle分页是通过rownum进行的,而rownum是从1开始的
int offset = (page.getPageNo() - 1) * page.getPageSize() + 1;
sqlBuffer.insert(0, "select tt.*, rownum rn from (").append(") tt where rownum <= ").append(offset + page.getPageSize()-1);
sqlBuffer.insert(0, "select * from (").append(") where rn >= ").append(offset);
// 上面的Sql语句拼接之后大概是这个样子:
// select * from (select tt.*, rownum rn from (select * from t_user) tt
// where rownum <= 30) where rn >= 16
return sqlBuffer.toString();
}
private void setTotalRecord(Page page, MappedStatement mappedStatement, Connection connection) {
// 获取对应的BoundSql,这个BoundSql其实跟我们利用StatementHandler获取到的BoundSql是同一个对象。
// delegate里面的boundSql也是通过mappedStatement.getBoundSql(paramObj)方法获取到的。
BoundSql boundSql = mappedStatement.getBoundSql(page);
// 获取到我们自己写在Mapper映射语句中对应的Sql语句
String sql = boundSql.getSql();
// 通过查询Sql语句获取到对应的计算总记录数的sql语句
String countSql = this.getCountSql(sql);
// 通过BoundSql获取对应的参数映射
List
// 利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象page建立查询记录数对应的BoundSql对象。
BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page);
// 通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, countBoundSql);
// 通过connection建立一个countSql对应的PreparedStatement对象。
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = connection.prepareStatement(countSql);
// 通过parameterHandler给PreparedStatement对象设置参数
parameterHandler.setParameters(pstmt);
// 之后就是执行获取总记录数的Sql语句和获取结果了。
rs = pstmt.executeQuery();
if (rs.next()) {
int totalRecord = rs.getInt(1);
// 给当前的参数page对象设置总记录数
page.setTotalRecord(totalRecord);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
if (pstmt != null)
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
private String getCountSql(String sql) {
int index = sql.indexOf("from");
return "select count(1) " + sql.substring(index);
}
public String getDatabaseType() {
return databaseType;
}
public void setDatabaseType(String databaseType) {
this.databaseType = databaseType;
}
}



