一、开发项目的搭建
1、创建基于 MyBatis 框架的 Maven 项目;
2、配置 pom.xml 文件,追加 MyBatis 开发包;
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
复制代码
3、设置 MyBatis 核心内容配置文件,核心内容包括:
核心内容文件置于 resources 目录下。
配置范例:
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--
using org.gjt.mm.mysql.Driver class before driver version 6.0.X
It depends on MySql.
When use MySql 5.7.+, com.mysql.cj.jdbc.Driver should be used as driver class.
-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!--
Url format depends on MySql.
When use MySql 5.7.+, configuration of serverTimezone and useSSL should be added.
Based on inner system interaction, SSL is closed.
-->
<property name="url" value="jdbc:mysql://localhost:3306/owndb?useSSL=false&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="admin"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/fuys/owndb/vo/mapping/User.xml"/>
</mappers>
</configuration>
复制代码
说明:
,表示的是数据库的连接类型。
POOLED,表示使用数据库连接池的方式进行数据库连接,性能最好;
JNDI,使用服务器上配置好的数据库连接池进行连接;
UNPOOLED,表示不适用数据库连接池进行连接,而是在需要使用时,创建一个新的连接,性能最差。
,表示事务管理。
4、添加 MySql 数据库连接支持
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
复制代码
5、添加数据库脚本,创建数据库表
drop database if exists owndb;
create database owndb char set utf8;
use owndb;
drop table if exists user;
create table user(
uid int auto_increment,
name varchar(255) not null,
constraint pk_uid primary key(uid)
) engine InnoDB;
alter table user add sex int;
复制代码
其中,engine InnoDB 表示支持事务处理。
二、实现数据操作
1、根据 ORM 映射关系,创建对象的 VO 类。
package org.fuys.owndb.vo;
import java.io.Serializable;
@SuppressWarnings("serial")
public class User implements Serializable {
private String name;
// getter和setter略
}
复制代码
2、资源目录下创建于 vo 类对应的映射文件。
namespace 是必须要求的,这样可以避免混淆。同时,可以使用完全限定的名称超越简化的声明,因为可以与接口绑定。
<?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="org.fuys.owndb.vo.mapping.User">
<insert id="insert" parameterType="org.fuys.owndb.vo.User">
insert user(name) values(#{name})
</insert>
</mapper>
复制代码
3、核心配置文件(mybatis-config.xml)添加 vo 类映射文件的注册配置,让 MyBatis 能够找到映射文件的位置。
<mappers>
<mapper resource="org/fuys/owndb/vo/mapping/User.xml"/>
</mappers>
复制代码
4、进行数据库的操作,任意一个 MyBatis 程序都是围绕 SqlSessionFactory 展开的,主要依赖以下几个类:
MyBatis 资源文件通过 Reader、InputStream 读取;
MyBatis 的连接需要通过连接工厂取得,连接工厂即:org.apache.ibatis.session.SqlSessionFactory;
MyBatis 具体的连接使用 org.apache.ibatis.session.SqlSession;
图 MyBatis 执行流程
Java 代码示例
package org.fuys.owndb.test;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.fuys.owndb.vo.User;
import org.junit.Test;
import junit.framework.TestCase;
public class UserVoTest {
@Test
public void testInsert() throws Exception{
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = sqlFactory.openSession();
User user = new User();
user.setName("jackson");
int len = session.insert("org.fuys.owndb.vo.mapping.User.insert",user);
TestCase.assertEquals(len, 1);
session.commit();
session.close();
is.close();
}
}
复制代码
5、范围(scope)和生命周期(lifecycle)
依赖注入框架能够创建线程安全的、事务的 sqlsession 和映射器(mapper)。同时,能够将他们直接注入到 bean 中。但这样往往会让开发者忽略了对象是具有生命周期的。
SqlSessionFactoryBuilder,只要创建了 SqlSessionFactory 类对象,便不在需要,其范围是方法范围(method scope)。
SqlSessionFactory,应用程序执行期间,该工厂类一直存在,也不用多次重新创建。因此,该工厂类的范围是应用范围(application scope)。
SqlSession,每一个线程拥有独立的 SqlSession 实例,由此,SqlSession 是非线程安全的。最佳的范围是方法范围,也最好不要一直长期保持对 SqlSession 的引用。建议在代码的最后,加上 SqlSession 的关闭操作。
三、自定义 SessionFactory 工厂
考虑到代码的重用性,MyBatis 的数据库操作的固化形式,为了简化 SqlSessionFactory 和 SqlSession 代码编写,可以编写自定义的工厂类。
图 自定义 MyBatis 的 SessionFactory 工厂类
自定义的 SessionFactory 工厂类需要实现以下几个功能点:
工厂类:
package org.fuys.owndb.model;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 1,get SqlSessionFactory and SqlSession object<br>
* 2,close SqlSession connection<br>
* 3,rebuild SqlSessionFactory
* @author ys
*
*/
public class MyBatisSqlSessionFactory {
/**
* Define logger
*/
private static Logger logger = LoggerFactory.getLogger(MyBatisSqlSessionFactory.class);
/**
* Define MyBatis-config.xml path
* JVM read from classPath
*/
private static String MYBATIS_CONFIG = "mybatis-config.xml";
/**
* Define inputStream to read mybatis-config.xml file
*/
private static InputStream is = null;
/**
* Define sqlSessionFactory object
*/
private static SqlSessionFactory sqlSessionFactory = null;
/**
* Ensure sqlSessionFacoty object is not null
*/
static{
rebuildSqlSessionFactory();
}
/**
* Define local thread to save SqlSession object
*/
private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<>();
/**
* Rebuild sqlSessionFactory object
*/
public static void rebuildSqlSessionFactory(){
try {
if(sqlSessionFactory==null){
is = Resources.getResourceAsStream(MYBATIS_CONFIG);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
}
} catch (Exception e) {
logger.error("rebuild SqlSessionFactory Exception", e);
}finally{
try {
is.close();
} catch (Exception e2) {
logger.error("close inputStream", e2);
}
}
}
/**
* Get sqlSessionFactory object
* @return
*/
public static SqlSessionFactory getSqlSessionFactory(){
return sqlSessionFactory;
}
/**
* Get sqlSession object
* @return
*/
public static SqlSession getSqlSession(){
SqlSession sqlSession = threadLocal.get();
if(sqlSession==null){
if(sqlSessionFactory==null){
rebuildSqlSessionFactory();
}
sqlSession = sqlSessionFactory.openSession();
threadLocal.set(sqlSession);
}
return sqlSession;
}
/**
* Close SqlSession object
*/
public static void closeSqlSession(){
SqlSession sqlSession = threadLocal.get();
if(sqlSession!=null){
sqlSession.close();
threadLocal.remove();
}
}
}
复制代码
测试工厂类:
package org.fuys.owndb.test;
import org.apache.ibatis.session.SqlSession;
import org.fuys.owndb.model.MyBatisSqlSessionFactory;
import org.fuys.owndb.vo.User;
import org.junit.Test;
import junit.framework.TestCase;
public class TestMyBatisSqlSessionFactory {
@Test
public void test() {
User user = new User();
user.setName("Yao Ming");
user.setSex(1);
SqlSession session = MyBatisSqlSessionFactory.getSqlSession();
int len = session.insert("org.fuys.owndb.vo.mapping.User.insert",user);
session.commit();
MyBatisSqlSessionFactory.closeSqlSession();
TestCase.assertEquals(len, 1);
}
}
复制代码
四、数据库 CRUD 操作
对于持久层框架而言,最基本的操作便是 CRUD 处理,这是最最基本功。CRUD 也是实体层最需要去实现的功能。
整个 CRUD 的配置与代码添加,其运作流程已在文章中的第二部分(实现数据操作)说明,在此不再赘述。 以下针对各个环节提出一些注意点,在未来的开发中,得以避免相关问题的发生。
4.1、数据增加
创建数据表,如下:
# goal table
drop table if exists goal;
create table goal(
goalId int auto_increment,
name varchar(30) not null,
description varchar(255),
start_time timestamp,
end_time timestamp,
constraint pk_goalId primary key (goalId)
)engine InnoDB;
复制代码
创建对应的 VO 类,如下:
package org.fuys.owndb.vo;
import java.io.Serializable;
import java.util.Date;
@SuppressWarnings("serial") // Different from specific value
public class Goal implements Serializable {
private String goalId;
private String name;
private String description;
private Date startTime;
private Date endTime;
// getter and setter method ....
}
复制代码
注意一:
当数据库表字段的属性名称包含有下划线“_”,但是对于 Java 规范的命名规则,建议对象变量属性的名称不要带有下划线“_”(当定义常量时可使用下划线“_”)。如 goal 表中字段 start_time 与 Goal 类的属性 startTime。
注意二:
可在核心内容配置文件(mybatis-config.xml)中添加 VO 类的别名,方便各个配置文件引用该 VO 类。
<typeAliases>
<typeAlias type="org.fuys.owndb.vo.Goal" alias="Goal"/>
</typeAliases>
复制代码
执行的测试程序:
@Test
public void testInsertGoal() throws Exception{
Goal goal = new Goal();
goal.setName("Executor" + new Random().nextInt(9999));
goal.setDescription("This is the most important goals in the world.");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
goal.setStartTime(sdf.parse("2018-01-01 00:00:00"));
goal.setEndTime(sdf.parse("2099-12-30 23:59:59"));
int len = MyBatisSqlSessionFactory.getSqlSession().insert("org.fuys.owndb.vo.mapping.Goal.insert", goal);
MyBatisSqlSessionFactory.getSqlSession().commit();
MyBatisSqlSessionFactory.closeSqlSession();
logger.info("Goal vo is " + goal);
TestCase.assertEquals(len, 1);
}
复制代码
测试结果:
INFO TestMyBatisSqlSessionFactory - Goal vo is Goal [goalId=null, name=Executor5271, description=This is the most important goals in the world., startTime=Mon Jan 01 00:00:00 CST 2018, endTime=Wed Dec 30 23:59:59 CST 2099]
复制代码
注意三:
为了达到简便多次,不修改传递参数代码的目的,可以在测试类中的传递对象参数,加上一个随机值,这样就不用多次去改变传递参数代码,减少测试繁琐事物。如:goal.setName("Executor" + new Random().nextInt(9999));
注意四:
测试程序结果中,VO 对象未返回生成记录的自增长主键值,可通过以下配置方式修改。当记录生成之后,即可获取返回自增长主键值。
<insert id="insert" parameterType="Goal" keyProperty="goalId" useGeneratedKeys="true">
insert goal(name,description,start_time,end_time)
values(#{name},#{description},#{startTime},#{endTime})
</insert>
复制代码
返回结果:
INFO TestMyBatisSqlSessionFactory - Goal vo is Goal [goalId=3, name=Executor8915, description=This is the most important goals in the world., startTime=Mon Jan 01 00:00:00 CST 2018, endTime=Wed Dec 30 23:59:59 CST 2099]
复制代码
4.2、数据修改和删除
注意:
MyBatis 框架中,所有的 CRUD 操作都是由 SqlSession 控制的。而接口中的 insert、update、delete 方法,实际上没有什么区别,可以互相使用,因为 JDBC 中 PreparedStatement 接口的 insert、update、delete 方法调用的都是 executeUpdate 方法。
4.3、数据查询
对于数据的查询,分为两种:
无论是查询单个还是查询多个,都需要利用反射机制实现 VO 转换。因此,需要保证 VO 的属性与数据库表中的属性保持一致。如若不一致,有两种方式解决:
<select id="selectByGoalId" parameterType="int" resultType="Goal">
SELECT goalId,name,description,start_time AS "startTime",end_time AS "endTime"
FROM goal
WHERE goalId = #{goalId}
</select>
复制代码
<resultMap type="Goal" id="GoalMap">
<id column="goalId" property="goalId"/>
<result column="name" property="name"/>
<result column="description" property="description"/>
<result column="start_time" property="startTime"/>
<result column="end_time" property="endTime"/>
</resultMap>
<select id="selectAll" resultMap="GoalMap">
SELECT goalId,name,description,start_time,end_time
FROM goal
</select>
复制代码
4.4、分页数据查询
注意一:
在整个项目开发中,对于分页数据的查询,一般需要完成两方面内容:
而我们往往会遇到分页的模糊查询,那么,对于传递的参数,需有:当前页、每页显示的行数、列名称、关键字。
Java 示例:
@Test
public void testSelectSplit(){
Map<String, Object> map = new HashMap<>();
map.put("column", "name");
map.put("keyword", "%%");
int count = MyBatisSqlSessionFactory.getSqlSession().selectOne("org.fuys.owndb.vo.mapping.Goal.selectCount",map);
TestCase.assertTrue(count>-1);
logger.info(String.valueOf(count));
map.put("start", 3);
map.put("linesize", 2);
List<Goal> goalList = MyBatisSqlSessionFactory.getSqlSession().selectList("org.fuys.owndb.vo.mapping.Goal.selectSplit",map);
TestCase.assertNotNull(goalList);
logger.info(String.valueOf(goalList.size()));
logger.info(goalList.toString());
}
复制代码
注意二:
${}:不会变为“?”进行解析,而是直接使用相关值。
#{}:会变为“?”进行解析,从而获取正确格式的值。
问题:
mapper 配置文件中,对于 delete 方法,怎样控制,可以使得开发不会因为程序漏洞而误删所有数据?
1、数据定期备份,可以暂时解决;
2、利用数据库的数据恢复功能,前提是数据库支持这一个功能;
3、可以限定 delete 的删除条件以及参数类型(如参数设置为 int,默认值为 0,不会删除所有数据),避免产生没有 where 条件而删除所有的情况。
四、使用 Java 程序实现 SqlSessionFactory
使用 Java 代码同样可以实现 SqlSessionFactory 功能,但是,需要注意,mapper 添加 Java 类,需要使用 Annotation。
代码示例如下:
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
复制代码
评论