一、开发项目的搭建
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 tabledrop 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 valuepublic 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);
复制代码
评论