图文教程,技术交流
实践与工具类封装
上一篇文章我们学习了MyBatis框架的环境搭建以及对sqlsessionfactory有个大致的了解,这篇文章就要运用搭建好的环境进行增删改查并且指出一些细节。
同样我们会进行mybatis工具类的封装,把共同的生成sqlSession的代码进行提取封装成工具类,需要sqlSession直接去工具类拿就可以了。文章中我们还可以学习到一些关于日志处理的知识,初步对单元测试的使用。
我们的案例使用的简单的Java项目不是基于动态web项目,因为我们现在处于学习阶段,关于SSM框架的整合会在以后的文章中进行详细的讲解。
1.案例截图
2. User.java
public class User {
private Integer user_id;
private String user_name;
private String account;
private String password;
private Integer status;
}
注意:由于篇幅限制,我们对User属性的getter,setter和tostring方法进行省略
3. mybatis-config.xml
<?xml version="1.0"encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTDConfig 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置环境 -->
<environments default="jujidi">
<environment id="jujidi">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/dbname"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 加载映射文件 -->
<mappers>
<mapper resource="com/jujidi/test/TestMapper.xml" />
</mappers>
</configuration>
这是Mybatis框架的核心配置文件,其中配置了相关环境和加载映射文件,环境中确定数据源,在数据源中配置你需要链接数据库的相关信息,比如用户名、密码。映射文件就是你编写SQL的xml,通过此核心配置文件进行加载关联。
注意这里的关键点:
-
默认的环境 ID(default="development")
-
每个 environment 元素定义的环境 ID
-
事务管理器的配置(type="JDBC")
-
数据源的配置(type="POOLED")
可以对环境随意命名,但一定要保证默认的环境 ID 要只匹配其中一个环境 ID
4. TestMapper.xml
<?xml version="1.0"encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTDMapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jujidi.model.User">
<!-- 通过id返回数据 -->
<select id="load" parameterType="integer" resultType="com.jujidi.test.User">
select * from user where user_id=#{user_id}
</select>
<select id="userList" resultType="map">
select * from user
</select>
<select id="count" resultType="integer">
select count(*) from user
</select>
<insert useGeneratedKeys="true" keyProperty="user_id" id="add" parameterType="com.jujidi.test.User" >
insert into user(user_name,account,password) values (#{user_name},#{account},#{password})
</insert>
</mapper>
Mapper是映射文件,对应多个标签(可以理解为SQL),每一个标签有唯一的id用来定位。
上方的代码是此文章案例使用的配置,下面我们扩展MyBatis进行简单增删改查的mapper文件写法:
5. 映射文件写法扩展
插入:
<mapper namespace="com.jujidi.model.User">
<insert id="insertPerson" parameterType="com.jujidi.model.User">
INSERT INTO user(user_name,password) VALUES(#{name},#{password})
</insert>
</mapper>
<mapper namespace="需要实现接口的全限定类名">
<insert id="需要实现的接口里的方法名" parameterType="方法参数类型,如果是对象要写全限定类名">
INSERT sql命令(命令里通过#{}获取对象属性)
</insert>
<mapper>
查询:
<!-- 通过id返回数据 -->
<select id="load" parameterType="integer" resultType="com.jujidi.test.User">
select * from user where user_id=#{user_id}
</select>
<select id="方法名" parameterType="方法参数类型" resultType="方法返回值类型,全类名">
SELECT 表里字段名 AS 结果字段名 FROM 表名 WHERE 条件
<!--注意:结果字段名与属性名保持一致,区分大小写-->
</select>
修改:
<update id="updateUserById" parameterType="com.jujidi.test.User">
update user
set name=#{name},status=#{status}
where user_id=#{user_id}
</update>
删除:
<delete id="deleteUserById"parameterType="int">
delete from user
where user_id=#{id}
</delete>
关于传递参数:
<select id="load" parameterType="integer" resultType="com.jujidi.test.User">
select * from user where user_id=#{user_id}
</select>
通过上面几行代码描述:在mybatis映射文件的配置中,有select等标签都提到了parameterType属性,parameterType为输入参数,在配置的时候,配置相应的输入参数类型即可。
parameterType有基本数据类型和复杂的数据类型配置:
1.基本数据类型,如输入参数只有一个,其数据类型可以是基本的数据类型
2.复杂数据类型:包含java实体类,map
6. 映射文件配置详解
<select id="load" parameterType="integer" resultType="com.jujidi.test.User">
select * from user where user_id=#{user_id}
</select>
<select id="userList" resultType="map">
select * from user
</select>
第一个select标签:此标签id为load,传入integer类型的user_id,去user表中查询此id的用户信息,把结果返回。返回值的类型为User对象。
第二个select标签:此标签id为userList,不需要参数,去user表中查询所有用户信息,进行结果反回。返回值的类型为map。
上方的两条select语句中,有一个属性是:resultType是SQL映射文件中定义返回值类型,返回值有基本类型,对象类型,List类型,Map类型等。
-
基本类型:resultType=基本类型
-
List类型:resultType=List中元素的类型
-
Map类型
-
单条记录:resultType=map
-
多条记录:resultType =Map中value的类型
代码中第一条select语句的resultType使用的对象类型,resultType直接写的对象User的全类名,而第二条select语句返回类型使用的是map,显然效果都是一样的。
7. 为什么是使用map而不是Map呢?
刚刚配置中的resultType="map",为什么是map,不是Map呢?
如果想写Map,需要写成全限定类名,即:resultType = "java.util.Map" map是java.util.Map的一个简写,还有其他的简写可以参考下方表格:
我们看下方两个语句的resultType:
<select id="load" parameterType="integer" resultType="com.jujidi.test.User">
select * from user where user_id=#{user_id}
</select>
<select id="userList" resultType="map">
select * from user
</select>
对于resultType属性,填写User的全类名和map类型效果都一样,还是有一些区别的。比如:如果使用User的全类名作为resultType的参数的话,我们需要保证实体类的属性名字和数据库字段的名字相同,否则的话是接不到值的。
想要实体类的属性和数据库字段不相同,我们有办法解决,可以通过更改SQL语句(别名)等方式来达到目的。
除了resultType属性外还有一个叫做:resultMap,它们俩的区别在后面的文章进行讲解。
8. MyBatisTest.java
public class MybatisTest {
public static void main(String[] args ) {
SqlSession sqlSession = null;
try{
//加载核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//创建sqlsession工厂 -->相当于connection
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//获取sqlsession -->相当于执行sql语句对象
sqlSession = sqlSessionFactory.openSession();
//执行sql
User user = sqlSession.selectOne("com.jujidi.model.User.load",1);
System.out.println(user);
List<Map<String,Object>> userList = sqlSession.selectList("com.jujidi.model.User.userList");
System.out.println(userList);
Integer count =sqlSession.selectOne("com.jujidi.model.User.count");
System.out.println(count);
}catch( Exception e){
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(sqlSession!=null){
sqlSession.close();
}
}
}
}
上方是测试类,测试类中我们加载核心配置文件、利用核心配置文件的信息来获取sqlsession工厂,工厂生成sqlsession。然后定位映射文件Mapper中的SQL语句进行执行相应查询方法。
9. 事务的提交
测试类中执行下方的insert方法:
public class MybatisTest {
public static void main(String[] args ) {
SqlSession sqlSession = null;
try{
//加载核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//创建sqlsession工厂 -->相当于connection
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//获取sqlsession -->相当于执行sql语句对象
sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setAccount("admin");
user.setPassword("123");
user.setUser_name("王久一");
sqlSession.insert("com.jujidi.model.User.add",user);
}catch( Exception e){
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(sqlSession!=null){
sqlSession.close();
}
}
}
}
Mapper.xml中语句为:
<insert useGeneratedKeys="true" keyProperty="user_id" id="add" parameterType="com.jujidi.test.User" >
insert into user(user_name,account,password) values (#{user_name},#{account},#{password})
</insert>
当我们执行insert方法时候,我们会发现:运行期间没有报错,但是数据库中并没有多出刚刚插入的记录。
这是因为进行了插入事务,事务并没有提交。不像以前学习的JDBC会自动提交事务。当我们利用MyBatis执行插入语句后,好像我们使用Navicat工具修改了表中的数据没有点保存一样。必须手动进行提交事务,我们对代码做出如下修改:
MybatisTest .java
public class MybatisTest {
public static void main(String[] args ) {
SqlSession sqlSession = null;
try{
//加载核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//创建sqlsession工厂 -->相当于connection
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//获取sqlsession -->相当于执行sql语句对象
sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setAccount("admin");
user.setPassword("123");
user.setUser_name("王久一");
sqlSession.insert("com.jujidi.model.User.add",user);
sqlSession.commit();
System.out.println(user.getUser_id());
}catch( Exception e){
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(sqlSession!=null){
sqlSession.close();
}
}
}
}
加上了sqlSession.commit()语句,这时候就可以成功的修改表中的数据。还有一个比较神奇的小细节:在我们还没有添加sqlSession.commit()语句时,虽然运行后没办法真正插入一条数据,但是想要插入数据的ID还是会生成的。
10. 获取ID
当我们插入了一条数据,怎么来获得新插入的数据的id?
再回到这条mapper语句:
<insert useGeneratedKeys="true" keyProperty="user_id" id="add" parameterType="com.jujidi.test.User" >
insert into user(user_name,account,password) values (#{user_name},#{account},#{password})
</insert>
上方的insert标签属性添加了:
useGeneratedKeys="true" keyProperty="user_id"
这两个属性后,执行下方的代码:
sqlSession.insert("com.jujidi.model.User.add",user);
sqlSession.commit();
System.out.println(user.getUser_id());
提交插入一条记录后,可以通过user.getUser_id()来获取刚插入记录的ID.
11. MyBatis工具类的封装
我们发现当使用Mybatis进行操作时,加载核心配置文件、创建sqlsession工厂等都是重复的代码,于是我们想着自己封装一个Mybatis工具类,此工具类负责返回一个sqlsession和一些关闭会话的操作。
工具类:MybatisUtil.java
public final class MybatisUtil {
private MybatisUtil(){}
private static final String PATH = "mybatis-config.xml";
private static InputStream is = null;
private static SqlSessionFactory sqlSessionFactory = null;
static{
try{
//加载核心配置文件
is = Resources.getResourceAsStream(PATH);
//创建sqlsession工厂 -->相当于connection
sqlSessionFactory = newSqlSessionFactoryBuilder().build(is);
}catch(Exception e){
e.printStackTrace();
throw new RuntimeException("加载映射文件失败可能是你的映射文件写错了原因:"+e.getMessage());
}
}
//获取sqlsession -->相当于执行sql语句对象
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
public static void closeSqlSession(SqlSession sqlSession){
if(sqlSession!=null){
sqlSession.close();
}
}
}
对于上方配置文件的关键字做出一些解释:
11.1 不可继承
final:不可以被继承
final:修饰一个变量path,变成常量不可以修改其值
11.2 不可实例化
private MybatisUtil(){}
工具类类名是:MybatisUtil。构造方法是用public修饰,但是上方使用了private。我们知道构造方法是用来实例化对象的,private来修饰说明本类不能被实例化。
11.3 初始化
static{
try{
...
}catch(IOException e){
...
}
}
上方代码:类加载就会加载静态代码块的代码 ,类初始化的时候就加载配置文件获取工厂。
11.4. 异常处理
catch(IOException e){
e.printStackTrace();
throw new RuntimeException("加载映射文件失败可能是你的映射文件写错了原因:"+e.getMessage());
}
对于catch到的异常 "加载映射文件失败,可能是映射文件写错了"要解释一下:
1. mybatis-config 配置了映射文件,映射文件配置出错,加载核心配置文件的时候也会报错
2. 为什么不提示mybatis-config.xml写错了呢?因为相应的环境参数配置一次就完毕,就是有错误调试成功以后很少会改动xml,而映射文件是常常需要我们编写修改的,所以抛出这样的提示更利于报错的时候准确找到错误。
12.单元测试
我们知道main()方法只能有一个,如果都在main方法里面进行测试代码,就需要注释掉测试完毕的方法,仅仅运行自己当前需要测试的功能方法,这样显得杂乱无章,所以我们学习一下单元测试,不仅对以后的学习有帮助,在工作中也是经常用到。
12.1 新建java工程
添加JUnit相关Jar包
右键项目:Build Path->Configure Builde Path->Add Library->JUnit->Next->Finish
Test01 .java
public class Test01 {
@Test
public void test01(){
System.out.println("王久一");
}
@Test
public void test02(){
System.out.println("小小詹");
}
@Test
public void test03(){
System.out.println("老邓头");
}
}
12.2 测试运行
对于上方代码:我们测试test01()方法,只需要选中test01:
右键->Run as->JUnit Test:
控制台便会打印出:王久一
以此类推,我们分别运行test02和test03方法,也会有相同的效果。
接下来修改代码:
public class Test01 {
@Before
public void before(){
System.out.println("Web项目聚集地before");
}
@After
public void after(){
System.out.println("Web项目聚集地after");
}
@Test
public void test01(){
System.out.println("王久一");
}
@Test
public void test02(){
System.out.println("小小詹");
}
@Test
public void test03(){
System.out.println("老邓头");
}
}
12.3 再次运行
测试test01()方法,控制台打印为:
添加@Before和 @After注解后,产生的效果可想而知。
12.4 一起运行
如果不单个运行,一起运行是什么效果呢?
13. log4j日志包
最后方便开发调试,我们需要增加日志功能:
导入log4j包
在classpath下添加log4j.properties文件:
# Global loggingconfiguration
log4j.rootLogger=ERROR,stdout
# 注意log4j.logger后面是需要日志处理mapper的namespace名称
log4j.logger.com.jujidi.model.User=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p[%t]-%m%n
通过第四行的配置和映射文件关联,在执行相关联mapper中的SQL就会在控制台打印出日志信息。
重要说明:实际开发中是不会使用Mybatis工具类来进行开发的,而是SSM框架整合后,通过接口代理的方式来实现对数据库的操纵。
为了使初学者对Mybatis框架有一个独立且系统的认识,所以采用这种系统的教学方式,而不是一开始就是整合。我们会在学习过程中逐渐拓展,最终达到实际开发所需要的本领。
如果您觉得此平台教程不错的话,欢迎推荐给自己的朋友!
推荐阅读
1. MyBatis框架教程「入门起步」
2.「SSM实战」从零开发内容管理系统
3. 从零开发一个JavaWeb项目要点「建议收藏」
4. Maven学习笔记(二)
本文分享自微信公众号 - Java后端(web_resource)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。