一、mybatis简介
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。 MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatement、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
二、mybatis入门
1、使用jdbc操作数据库存在的问题
JDBC编程步骤:
加载数据库驱动
创建并获取数据库连接
创建jdbc statement对象
设置SQL语句
传递SQL语句参数
执行statement获取结果
解析SQL执行结果
释放资源
jdbc程序
|
|
jdbc存在的问题
数据库连接创建,释放频繁造成系统资源浪费从而影响系统性能,可以使用数据库连接池技术来解决这个问题。
SQL语句在代码中硬编码,不便于维护,实际应用中修改SQL语句需要修改java代码,不便于修改。
使用preparedStatement语句向占位符传参存在硬编码,实际上SQL语句的where条件不固定,修改困难
对结果集存在硬编码(查询列名),SQL变化导致解析代码变化,系统不易维护,将数据库记录封装成pojo对象解析比较方便。
2、mybatis架构
mybatis配置
- SqlMapConfig.xml为mybatis的全局配置文件,配置了mybatis的运行环境等信息。
- mapper.xml为SQL映射文件,文件中配置了操作数据库的SQL语句,需要加载在SQLMapConfig.xml文件中。
通过mybatis环境等配置信息构造SqlSessioNFactory,也就是会话工厂。
由会话工厂创建SqlSession,操作数据库需要通过会话来实现。
mybatis低层自定义了Executor执行器操作数据库,Execotor接口有两个实现,一个是基本执行器,一个是缓存执行器。
MappedStatement也是mybatis的一个低层封装对象,它包装了mybatis配置信息及SQL映射信息等。mapper.xml文件中一个SQL对应一个MappedStatement对象,SQL的id即MappedStatement的id。
MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。
MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
3、mybatis的入门程序
1、mybatis下载
mybatis的代码由GitHub管理,地址为:https://github.com/mybatis/mybatis-3/releases
文件结构为:
2、实现的功能
根据用户id查询一个用户信息
根据用户名称模糊查询用户信息列表
添加用户
更新用户
删除用户
3、工程搭建
使用IDEA 2017.2 创建Java工程,jdk为1.8.0_141
添加jar包,包括mybatis核心包,依赖包,数据库驱动包
日志的打印:mybatis默认使用log4j输出日志信息,在classpath下创建log4j.properties:
123456# Global logging configurationlog4j.rootLogger=DEBUG, stdout# Console output...log4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%nSqlMapConfig.xml文件:配置了数据源,事务管理
1234567891011121314151617181920<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><!-- 和spring整合后 environments配置将废除--><environments default="development"><environment id="development"><!-- 使用jdbc事务管理--><transactionManager type="JDBC" /><!-- 数据库连接池--><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}" /><property name="url" value="${jdbc.url}" /><property name="username" value="${jdbc.username}" /><property name="password" value="${jdbc.password}" /></dataSource></environment></environments></configuration>po类的编写:po类作为mybatis进行SQL映射使用,po类通常与数据表对应,User.java如下:
12345678910111213package cn.itheima.pojo;import java.util.Date;import java.util.List;public class User {private int id;private String username;// 用户姓名private String sex;// 性别private Date birthday;// 生日private String address;// 地址//omit the getter and setter...}编写SQL映射文件:User.xml, namespace用于隔离SQL语句
12345678<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--命名空间的作用为SQL隔离--><mapper namespace="test"></mapper>加载映射文件,将User.xml添加在SQLMapConfig.xml中:
123<mappers><mapper resource="User.xml"/></mappers>
4、根据id查询用户信息
在user.xml中添加SQL语句:
12345678<!--根据id获取用户信息--><!--parameterType指定传入参数类型,resultMap指定结果集类型#{}为占位符,如果传入的是基本类型,#{}中的变量名称可以随意指定--><select id="findUserById" parameterType="java.lang.Integer" resultType="cn.itheima.pojo.User">select * from user where id = #{id}</select>测试程序:
123456789101112131415161718192021222324252627282930private SqlSessionFactory sqlSessionFactory;@Beforepublic void createSqlSessionFactory() throws IOException {//配置文件String resource = "SqlMapConfig.xml";InputStream inputStream = Resources.getResourceAsStream(resource);//使用SqlSessionFactoryBuilder从XML配置文件中创建sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);}@Testpublic void testFindUserById() {SqlSession sqlSession = null;try {//创建会话sqlSession = sqlSessionFactory.openSession();//第一个参数,namespace+idUser user = sqlSession.selectOne("test.findUserById", 1);System.out.println(user);} catch (Exception e) {e.printStackTrace();} finally {if (sqlSession != null) {sqlSession.close();}}}
5、根据用户名查询用户信息
在url.xml中添加SQL语句:
123456<!--如果返回结果为集合,则可以调用session中的selectList方法,resultType仍然为--><!--${}拼接符,如果传入的是基本类型,其内部的变量名称必须为value--><!--拼接符有SQL注入的风险,慎重使用,--><select id="findUserByUserName" parameterType="java.lang.String" resultType="cn.itheima.pojo.User">select * from user where username like '%${value}%'</select>测试程序:
12345678910111213141516171819private SqlSessionFactory sqlSessionFactory;@Beforepublic void createSqlSessionFactory() throws IOException {//配置文件String resource = "SqlMapConfig.xml";InputStream inputStream = Resources.getResourceAsStream(resource);//使用SqlSessionFactoryBuilder从XML配置文件中创建sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);}@Testpublic void testFindUserByUserName() throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();List<User> users = sqlSession.selectList("test.findUserByUserName", "王");for (User user : users) {System.out.println(user);}}
6、概念比较
#{}和${}
#{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止SQL注入。#{}可以接收简单类型值或pojo属性值。如果parameterType传输单个简单类型值,#{}括号中可以是value或其他属性。
${}表示拼接字符串,通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。
parameterType和resultType
parameterType:指定输入参数类型,mybatis通过OGNL表达式从对象中获取参数值拼接在SQL中。
resultType:指定输出结果类型,mybatis将SQL查询结果的一行记录数据映射为resultType指定类型的对象。
selectOne和selectList
selectOne:查询一条记录,使用其查询多条记录会抛出异常
selectList:查询一条或者多条记录
7、添加用户
在SQLMapConfig.xml中添加:
12345678910111213<!--如果从传入的是pojo类型,name#{}中对应的属.属性--><!--如果要返回数据库自增组件,可以使用select last_insert_id()函数--><insert id="insertUser" parameterType="cn.itheima.pojo.User"><!--执行数据库函数,返回最近增加的自增主键idkeyProperty:将返回的主键放入传入的参数的ID中保存order:当前函数相对于insert语句的执行顺序resultType:keyProperty中的数据的类型--><selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">select last_insert_id()</selectKey>insert into user (username, birthday, sex, address) values (#{username}, #{birthday}, #{sex}, #{address})</insert>测试程序
123456789101112131415161718//omit the before@Testpublic void testInsertUser() throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();//创建插入数据库的用户对象User user = new User();user.setUsername("aaa");user.setBirthday(new Date());user.setSex("1");user.setAddress("北京");System.out.println(user);//执行SQL语句插入对象sqlSession.insert("test.insertUser", user);//提交事务,mybatis会自动开启事务,需要手动提交事务sqlSession.commit();System.out.println(user);}uuid实现主键:AFTER改为BEFORE,则在插入之前生成UUID并保存到数据库中
12345678<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User"><selectKey resultType="java.lang.String" order="BEFORE"keyProperty="id">select uuid()</selectKey>insert into user(id,username,birthday,sex,address)values(#{id},#{username},#{birthday},#{sex},#{address})</insert>
8、删除用户
在SQLMapConfig.xml中添加:
123<delete id="deleteUserById" parameterType="java.lang.Integer">delete from user where id=#{id}</delete>测试程序
123456789//omit the before@Testpublic void testDeleteUserById() throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();//删除sqlSession.delete("test.deleteUserById", 28);//提交sqlSession.commit();}
9、修改用户
在SQLMapConfig.xml中添加:
123<update id="updateUserById" parameterType="cn.itheima.pojo.User">update user set username = #{username} where id = #{id}</update>测试程序
123456789101112@Testpublic void testUpdateUserById() throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();User user = new User();user.setId(29);user.setUsername("王麻子");sqlSession.update("test.updateUserById", user);sqlSession.commit();}
10、mybatis解决jdbc编程的问题
数据库连接创建,释放频繁造成系统资源浪费从而影响系统性能,可以使用数据库连接池技术来解决这个问题。
解决:在SQLMapConfig.xml中配置数据连接池,使用连接池管理数据库连接
SQL语句在代码中硬编码,不便于维护,实际应用中修改SQL语句需要修改java代码,不便于修改。
解决:将SQL语句配置在XXmapper.xml文件中与java程序分离
使用preparedStatement语句向占位符传参存在硬编码,实际上SQL语句的where条件不固定,修改困难
解决:mybatis自动将java对象映射至SQL语句,通过statement中的parameterType定义输入参数的类型
对结果集存在硬编码(查询列名),SQL变化导致解析代码变化,系统不易维护,将数据库记录封装成pojo对象解析比较方便。
解决:mybatis自动将SQL执行结果映射至java对象,通过statement的resultType定义输出结果的类型
11、mybatis和hibernate的区别
Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。
Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。
Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
三、DAO的开发方法
使用mybatis开发DAO,有两种方法,原始DAO方法和Mapper接口开发方法
1、需求
根据用户id查询一个用户信息
根据用户名称模糊查询用户信息列表
添加用户信息
2、SqlSession的使用范围
SqlSession中封装了对数据库的操作,如:查询、插入、更新、删除等。通过SqlSessionFactory创建SqlSession,而SqlSessionFactory是通过SqlSessionFactoryBuilder进行创建。
SqlSessionFactoryBuilder:用于创建SqlSessionFacoty,SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory生产,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。
SqlSessionFactory:SQLSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个运行期间,一旦创建之后可以重复使用,通常以单例模式管理SqlSessionFactory。
SqlSession: SqlSession是一个面向用户的接口,sqlSession中定义了数据库操作方法。每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。打开一个 SqlSession;使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以确保每次都能执行关闭。
3、原始DAO开发方式(需要编写接口和实现类)
映射文件:
12345678910111213141516171819202122<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--命名空间的作用为SQL隔离--><mapper namespace="test"><!--根据id获取用户信息--><select id="findUserById" parameterType="java.lang.Integer" resultType="cn.itheima.pojo.User">select * from user where id = #{id}</select><select id="findUserByUserName" parameterType="java.lang.String" resultType="cn.itheima.pojo.User">select * from user where username like '%${value}%'</select><insert id="insertUser" parameterType="cn.itheima.pojo.User"><selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">select last_insert_id()</selectKey>insert into user (username, birthday, sex, address) values (#{username}, #{birthday}, #{sex}, #{address})</insert></mapper>Dao接口:
1234567891011package cn.itheima.dao;import cn.itheima.pojo.User;import java.util.List;public interface UserDao {public User findUserById(Integer id);public List<User> findUserByUserName(String username);}实现类:
|
|
测试:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465package test.cn.itheima.dao;import cn.itheima.dao.UserDao;import cn.itheima.dao.UserDaoImpl;import cn.itheima.pojo.User;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.junit.Test;import org.junit.Before;import org.junit.After;import java.io.InputStream;import java.util.List;/*** UserDaoImpl Tester.** @author <Authors name>* @since <pre>一月 24, 2018</pre>* @version 1.0*/public class UserDaoImplTest {private SqlSessionFactory sqlSessionFactory;private UserDao userDao;@Beforepublic void before() throws Exception {String resource = "SqlMapConfig.xml";InputStream inputStream = Resources.getResourceAsStream(resource);sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);userDao = new UserDaoImpl(sqlSessionFactory);}@Afterpublic void after() throws Exception {}/**** Method: findUserById(Integer id)**/@Testpublic void testFindUserById() throws Exception {User user = userDao.findUserById(1);System.out.println(user);}/**** Method: findUserByUserName(String username)**/@Testpublic void testFindUserByUserName() throws Exception {List<User> users = userDao.findUserByUserName("王");for (User user : users) {System.out.println(user);}}}
4、动态代理方法
1、开发规范
Mapper接口开发方法只需要程序员编写Mapper接口,由mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上班DAO接口实现类方法。Mapper接口需要遵守以下规范:
Mapper.xml文件中的namespace与mapper接口的类路径相同
Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType类型相同。
2、Mapper.xml(映射文件)
定义mapper映射文件UserMapper.xml,需要修改namespace的值为UserMapper接口路径,经UserMapper.xml放在mapper路径下:
|
|
3、Mapper.java(接口文件)
|
|
接口定义有如下特点:
Mapper接口名和Mapper.xml中定义的statement的id相同
Mapper接口方法的输入参数类型和mapper.xml中定义的statement的parameterType的类型相同。
Mapper接口方法的输出参数类型和mapper.xml中定义的statement的resultType的类型相同
4、在SQLMapConfig.xml中配置映射文件
|
|
5、测试
|
|
6、小结
selectOne和selectList:动态代理对象调用sqlSession.selectOne()和sqlSession.selectList()是根据mapper接口的返回值决定,如果返回list则调用selectList方法,如果返回单个对象则调用selectOne方法。
namespace:mybatis官方推荐使用mapper代理方法开发mapper接口,程序员不用编写mapper接口实现类,使用mapper代理方法时,输入参数可以使用pojo包装对象或map对象,保证dao的通用性。
四、SqlMapConfig.xml文件说明
1、配置内容:
properties(属性)
settings(全局配置参数)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)
2、properties(属性)
SqlMapConfig.xml可以引用java属性文件中的配置信息,创建db.properties保存数据库属性:
修改SqlMapConfig.xml中引用:
|
|
其属性读取顺序为:
- 在properties元素体内定义的属性会首先被读取
- 然后会读取properties元素中的resource或url加载的属性,它会覆盖已经读取的同名属性。
3、typeAliases(类型别名)
1、mybatis支持别名:
别名 | 映射的类型 |
---|---|
_byte |
byte |
_long |
long |
_short |
short |
_int |
int |
_integer |
int |
_double |
double |
_float |
float |
_boolean |
boolean |
string |
String |
byte |
Byte |
long |
Long |
short |
Short |
int |
Integer |
integer |
Integer |
double |
Double |
float |
Float |
boolean |
Boolean |
date |
Date |
decimal |
BigDecimal |
bigdecimal |
BigDecimal |
map |
Map |
2、自定义别名:在SqlMapConfig.xml中配置:
|
|
4、Mappers(映射器)
: 使用相对于类路径的资源,如: :使用mapper接口类路径,如:
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
:注册指定包下的所有mapper接口,如:
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
5、传递pojo包装类内容(输入类型的一种)
开发中通过pojo传递查询条件,查询条件是综合的查询条件,不仅包括用户查询条件,还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。Pojo类中包含pojo。
需求:根据用户名查询用户信息,查询条件放到QueryVo的user属性中。
QueryVo:
12345678910111213141516171819202122232425package cn.itheima.pojo;import java.util.List;public class QueryVo {private User user;private List<Integer> ids;public List<Integer> getIds() {return ids;}public void setIds(List<Integer> ids) {this.ids = ids;}public User getUser() {return user;}public void setUser(User user) {this.user = user;}}sql语句
select * from user where username like '%王%'
Mapper文件
123<select id="findUserByVo" parameterType="cn.itheima.pojo.QueryVo" resultType="cn.itheima.pojo.User">select * from user where username LIKE '%${user.username}%' AND sex=#{user.sex}</select>接口
1public List<User> findUserByVo(QueryVo queryVo);测试:
12345678910111213141516@Testpublic void findUserByVo() throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);QueryVo queryVo = new QueryVo();User user = new User();user.setUsername("王");user.setSex("1");queryVo.setUser(user);List<User> users = userMapper.findUserByVo(queryVo);System.out.println(users);}
6、resultType(输出类型)
1、输出简单类型
|
|
|
|
|
|
2、输出pojo对象或者pojo对象列表
|
|
|
|
7、resultMap
resultType可以指定pojo将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功。如果sql查询字段名和pojo的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系 ,resultMap实质上还需要将查询结果映射到pojo对象中。resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。
Mapper.xml的定义:使用resultMap来指定输出内容映射
12345<select id="findOrdersAndUser2" resultMap="orderAndUserResultMap">select a.*, b.id uid, username, birthday, sex, addressfrom orders a, user bwhere a.user_id = b.id</select>定义resultMap
123456789101112131415161718192021222324<!--一对一,手动映射--><!--id resultMap的唯一标识,type为将查询出的数据放入这个指定的对象手动映射需要制定数据库中表的字段名与java中Pojo类的属性名称的对应关系--><resultMap id="orderAndUserResultMap" type="cn.itheima.pojo.Orders"><!--id标签制定主键字段对应关系, column为数据库中的字段名,property为pojo类的属性名--><id column="id" property="id"/><!--result标签制定非主键列的对应关系--><result column="user_id" property="userId"/><result column="number" property="number"/><result column="createtime" property="createtime"/><result column="note" property="note"/><!--user对象的映射关系的指定property:指定将数据放入Orders中的user属性中,type为user的类型--><association property="user" javaType="cn.itheima.pojo.User"><id column="uid" property="id"/><result column="username" property="username"/><result column="birthday" property="birthday"/><result column="sex" property="sex"/><result column="address" property="address"/></association></resultMap>Mapper接口定义
1public List<Orders> findOrdersAndUser2();
五、动态sql
通过mybatis提供的各种标签方法实现动态拼接SQL。
1、if
|
|
空字符串校验
2、where
|
|
会自动向SQL语句中添加where关键字,会去掉第一个条件的and关键字
3、foreach
向SQL传递数组或List,mybatis使用foreach解析,需求在传入多个id查询用户信息时需要。
需要在Vo中定义list属性ids存储多个用户id,并添加getter、setter方法。
123456789private List<Integer> ids;public List<Integer> getIds() {return ids;}public void setIds(List<Integer> ids) {this.ids = ids;}mapper.xml中的编写:
1234567891011121314151617<select id="findUserByIds" parameterType="cn.itheima.pojo.QueryVo" resultType="user">select * from user<where><if test="ids != null and ids.size > 0"><!--foreach:循环传入的集合参数collection:传入的集合的变量名称item:每次循环将循环出的数据放入这个变量中open:循环开始拼接的字符串close:循环结束拼接的字符串seperator:循环中拼接的字符串--><foreach collection="ids" item="id" open="and id in (" close=")" separator=",">#{id}</foreach></if></where></select>测试:
123456789101112131415161718192021@Testpublic void testFindUserByIds() throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);QueryVo vo = new QueryVo();List<Integer> ids = new ArrayList<>();ids.add(1);ids.add(10);ids.add(16);ids.add(28);ids.add(22);vo.setIds(ids);List<User> users = userMapper.findUserByIds(vo);for (User user : users) {System.out.println("===============" + user + "===============");}}
4、sql片段
sql中可将重复的SQL提取出来,使用时用include引用即可,从而达到SQL重用的目的:
六、关联查询
1、商品订单数据模型
2、一对一关联
需求:查询所有订单信息,关联查询下单用户信息。由于一个订单信息只能由一个用户下单,所以从查询订单信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的订单为一对多查询。
方法一:使用resultType,定义订单信息po类,此po类中包括了订单信息和用户信息。
SQL语句
123select a.*, b.id uid, username, birthday, sex, addressfrom orders a, user bwhere a.user_id = b.id定义po类:po类应该包含sql查询的所有字段
123456789101112package cn.itheima.pojo;import java.util.Date;public class CustomOrders extends Orders{private int uid;private String username;// 用户姓名private String sex;// 性别private Date birthday;// 生日private String address;// 地址//omit...OrdersCustomer类继承Orders类后包括了Orders类的所有字段,只需要定义用户的信息字段即可。
Mapper.xml中
123456<!--一对一,自动映射 --><select id="findOrdersAndUser1" resultType="cn.itheima.pojo.CustomOrders">select a.*, b.id uid, username, birthday, sex, addressfrom orders a, user bwhere a.user_id = b.id</select>Mapper接口
1public List<CustomOrders> findOrdersAndUser1();测试
1234567891011@Testpublic void testFindOrdersAndUser1() throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);List<CustomOrders> customOrders = userMapper.findOrdersAndUser1();for (CustomOrders customOrders1 : customOrders) {System.out.println("===============" + customOrders1 + "===============");}}小结:
定义专门的po类作为输出类型,其中定义了SQL查询结果集的所有字段,此方法比较简单,企业中使用普遍。
方法二:使用resultMap,定义专门的resultMap用于映射一对一查询
SQL语句
123select a.*, b.id uid, username, birthday, sex, addressfrom orders a, user bwhere a.user_id = b.id定义po类:在Orders类中加入User属性,User属性中用于存储关联查询的用户信息,因为订单关联查询是一对一关系,所以这里使用单个User对象存储关联查询的用户信息。
12345678910111213141516171819package cn.itheima.pojo;import java.util.Date;import java.util.List;public class Orders {private Integer id;private Integer userId;private String number;private Date createtime;private String note;private User user;//omit...}Mapper.xml中
1234567891011121314151617181920212223242526<resultMap id="orderAndUserResultMap" type="cn.itheima.pojo.Orders"><!--id标签制定主键字段对应关系, column为数据库中的字段名,property为pojo类的属性名--><id column="id" property="id"/><!--result标签制定非主键列的对应关系--><result column="user_id" property="userId"/><result column="number" property="number"/><result column="createtime" property="createtime"/><result column="note" property="note"/><!--user对象的映射关系的指定property:指定将数据放入Orders中的user属性中,type为user的类型--><association property="user" javaType="cn.itheima.pojo.User"><id column="uid" property="id"/><result column="username" property="username"/><result column="birthday" property="birthday"/><result column="sex" property="sex"/><result column="address" property="address"/></association></resultMap><select id="findOrdersAndUser2" resultMap="orderAndUserResultMap">select a.*, b.id uid, username, birthday, sex, addressfrom orders a, user bwhere a.user_id = b.id</select>- association:表示进行关联查询单条记录
- property:表示关联查询的结果存储在cn.itcast.mybatis.po.Orders的user属性中
- javaType:表示关联查询的结果类型
:查询结果的user_id列对应关联对象的id属性,这里是 表示user_id是关联查询对象的唯一标识。 :查询结果的username列对应关联对象的username属性
Mapper接口
1public List<Orders> findOrdersAndUser2();测试
123456789@Testpublic void testFindOrdersAndUser2() throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);List<Orders> orders = userMapper.findOrdersAndUser2();System.out.println(orders);}小结:使用association完成关联查询,将关联查询信息映射到pojo对象中。
3、一对多查询
需求:查询所有用户信息及用户关联的订单信息,用户信息和订单信息为一对多关系。使用resultMap实现:
SQL语句
12select a.*, b.id oid, user_id, number, createtimefrom user a, orders b where a.id = b.user_id定义po类:在User类中加入List
orders属性。 123456789101112131415package cn.itheima.pojo;import java.util.Date;import java.util.List;public class User {private int id;private String username;// 用户姓名private String sex;// 性别private Date birthday;// 生日private String address;// 地址private List<Orders> ordersList;//omit....}Mapper.xml中
1234567891011121314151617181920212223<resultMap id="userAndOrdersResultMap" type="cn.itheima.pojo.User"><id column="id" property="id"/><result column="username" property="username"/><result column="sex" property="sex"/><result column="birthday" property="birthday"/><result column="address" property="address"/><!--订单集合--><!--collection指定对应的集合关系映射property:将数据放入指定的User对象的OrdersList集合属性中ofType: 指定OrderList中的泛型类型--><collection property="ordersList" ofType="cn.itheima.pojo.Orders"><id column="oid" property="id"/><result column="user_id" property="userId"/><result column="number" property="number"/><result column="createtime" property="createtime"/></collection></resultMap><select id="findUserAndOrders" resultMap="userAndOrdersResultMap">select a.*, b.id oid, user_id, number, createtimefrom user a, orders b where a.id = b.user_id</select>- collections:定义了用户关联的订单信息,表示关联查询结果集
- property:关联查询的结果集存储在User对象上的那个属性
- ofType:指定关联查询的结果集中的对象类型及List中的对象类型。此处可以使用别名也可以使用全限定名
:查询结果的oid列对应关联对象的id属性,这里是 表示oid是关联查询对象的唯一标识。 :查询结果的userId列对应关联对象的user_id属性
Mapper接口
1public List<User> findUserAndOrders();测试
123456789@Testpublic void testFindUserAndOrders() throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);List<User> users = userMapper.findUserAndOrders();System.out.println(users);}
七、Spring整合mybatis
1、整合思路
SqlSessionFactory对象应该放入Spring容器中作为单例存在。
传统dao的开发方式中,应该从Spring容器中获取SqlSession对象。
Mapper代理形式中,应该从Spring容器中直接获得mapper对象。
数据库的链接记忆数据库连接池事务都交给spring容器来完成。
2、整合需要的jar包
Spring的jar包
mybatis的jar包
spring+mybatis的整合包
MySQL数据库驱动包
数据库连接池的jar包
3、整合的步骤
创建一个java工程
导入jar包
mybatis配置文件SQLMapConfig.xml
编写Spring配置文件,包括数据库连接及连接池、事务管理、SqlSessionFactory对象,配置到Spring容器中、,mapper代理对象或者dao实现类配置到Spring容器中。
编写dao或者mapper文件
测试
1、SqlMapConfig.xml
|
|
2、applicationContext.xml
|
|
3、db.properties
|
|
4、DAO的开发
三种dao的实现方式:
传统dao的开发方式
使用mapper代理形式开发方式
使用扫描包配置mapper代理
1、使用传统dao的开发方式
接口+实现类来完成。dao实现类需要继承SqlSessionDaoSupport.
实现类:
1234567891011121314151617181920212223242526272829package cn.itheima.dao;import cn.itheima.pojo.User;import org.apache.ibatis.session.SqlSession;import org.mybatis.spring.support.SqlSessionDaoSupport;import java.util.List;public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao{@Overridepublic User findUserById(Integer id) {//sqlSession是线程不安全的,所以他是最佳使用范围在方法体内SqlSession openSession = this.getSqlSession();User user = openSession.selectOne("test.findUserById", id);return user;}@Overridepublic List<User> findUserByUserName(String username) {SqlSession sqlSession = this.getSqlSession();List<User> users = sqlSession.selectList("test.findUserByUserName", username);return users;}}配置DAO:
1234<!--配置原生DAO实现--><bean id="userDao" class="cn.itheima.dao.UserDaoImpl"><property name="sqlSessionFactory" ref="sqlSessionFactory"/></bean>测试:
1234567891011121314151617181920212223242526272829package cn.itheima.test;import cn.itheima.dao.UserDao;import cn.itheima.pojo.User;import org.junit.Before;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class UserDaoTest {private ApplicationContext applicationContext;@Beforepublic void setUp() throws Exception {String configLocation = "applicationContext.xml";applicationContext = new ClassPathXmlApplicationContext(configLocation);}@Testpublic void testFindUserById() throws Exception {//获取DAO对象,getBean中的字符串为配置文件中声明的UserDao userDao = (UserDao) applicationContext.getBean("userDao");User user = userDao.findUserById(1);System.out.println(user);}}
2、Mapper代理形式开发dao
按照代理的要求,编写映射文件和接口,放置在同一个路径下。
配置
123456<!--Mapper接口代理的实现--><bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"><!–接口路径名–><property name="mapperInterface" value="UserMapper"/><property name="sqlSessionFactory" ref="sqlSessionFactory"/></bean>测试
|
|
3、使用包扫描的方式配置mapper
|
|
每个mapper代理对象的id就是类名首字母小写
八、mybatis逆向工程
使用官方网站的mapper自动生成工具mybatis-generator-core-1.3.2来生成po类和mapper映射文件。
作用:mybatis官方提供逆向工程,可以使用它通过数据库中的表来自动生成Mapper接口和映射文件(单表增删改查)和Po类.
需要导入的jar包包括:
1、mapper生成配置文件
在generatorConfig.xml中配置mapper生成的详细信息,需要注意:
添加要生成的数据库表
po文件所在的包路径
mapper文件所在的包路径
配置文件:
|
|
2、使用java类生成mapper文件
|
|
3、将生成的mapper文件拷贝到指定目录
注意:mapper xml文件和mapper.java文件在一个目录内且文件名相同。
4、测试mapper接口
|
|
5、注意事项
Mapper文件内容时追加不是覆盖。所以XXXMapper.xml文件已经存在时,如果进行重新生成则mapper.xml文件内容不被覆盖而是进行内容追加,结果导致mybatis解析失败。
解决方法:删除原来已经生成的mapper xml文件再进行生成。Mybatis自动生成的po及mapper.java文件不是内容而是直接覆盖没有此问题。Table Schema问题:
下边是关于针对oracle数据库表生成代码的schema问题:
Schma即数据库模式,oracle中一个用户对应一个schema,可以理解为用户就是schema。
当Oralce数据库存在多个schema可以访问相同的表名时,使用mybatis生成该表的mapper.xml将会出现mapper.xml内容重复的问题,结果导致mybatis解析错误。
解决方法:在table中填写schema,如下:
Oracle查询对象的schema可从dba_objects中查询,如下:
select * from dba_objects
九、总结
mybatis是一个持久层框架, 作用是跟数据库交互完成增删改查
原生Dao实现(需要接口和实现类)
动态代理方式(只需要接口)
mapper接口代理实现编写规则:- 映射文件中namespace要等于接口的全路径名称
- 映射文件中sql语句id要等于接口的方法名称
- 映射文件中传入参数类型要等于接口方法的传入参数类型
- 映射文件中返回结果集类型要等于接口方法的返回值类型
#{}占位符:占位
如果传入的是基本类型,那么#{}中的变量名称可以随意写
如果传入的参数是pojo类型,那么#{}中的变量名称必须是pojo中的属性.属性.属性…${}拼接符:字符串原样拼接
如果传入的是基本类型,那么${}中的变量名必须是value
如果传入的参数是pojo类型,那么${}中的变量名称必须是pojo中的属性.属性.属性…
注意:使用拼接符有可能造成sql注入,在页面输入的时候可以加入校验,不可输入sql关键字,不可输入空格映射文件:
- 传入参数类型通过parameterType属性指定
- 返回结果集类型通过resultType属性指定
hibernate和mybatis区别:
- hibernate:它是一个标准的orm框架,比较重量级,学习成本高.
优点:高度封装,使用起来不用写sql,开发的时候,会减低开发周期.
缺点:sql语句无法优化
应用场景:oa(办公自动化系统), erp(企业的流程系统)等,还有一些政府项目,总的来说,在用于量不大,并发量小的时候使用.
- mybatis:它不是一个orm框架, 它是对jdbc的轻量级封装, 学习成本低,比较简单
有点:学习成本低, sql语句可以优化, 执行效率高,速度快
缺点:编码量较大,会拖慢开发周期
应用场景: 互联网项目,比如电商,P2p等总的来说是用户量较大,并发高的项目.
- hibernate:它是一个标准的orm框架,比较重量级,学习成本高.
输入映射(就是映射文件中可以传入哪些参数类型)
- 1)基本类型
- 2)pojo类型
- 3)Vo类型
- 1)基本类型
输出映射(返回的结果集可以有哪些类型)
- 1)基本类型
- 2)pojo类型
- 3)List类型
动态sql:动态的拼接sql语句,因为sql中where条件有可能多也有可能少
- 1)where:可以自动添加where关键字,还可以去掉第一个条件的and关键字
- 2)if:判断传入的参数是否为空
- 3)foreach:循环遍历传入的集合参数
- 4)sql:封装查询条件,以达到重用的目的
对单个对象的映射关系:
- 1)自动关联(偷懒的办法):可以自定义一个大而全的pojo类,然后自动映射其实是根据数据库总的字段名称和pojo中的属性名称对应.
- 2)手动关联: 需要指定数据库中表的字段名称和java的pojo类中的属性名称的对应关系.使用association标签
对集合对象的映射关系
只能使用手动映射:指定表中字段名称和pojo中属性名称的对应关系使用collection标签spring和mybatis整合
整合后会话工厂都归spring管理* 1)原生Dao实现: 需要在spring配置文件中指定dao实现类 dao实现类需要继承SqlSessionDaoSupport超类 在dao实现类中不要手动关闭会话,不要自己提交事务. * 2)Mapper接口代理实现: 在spring配置文件中可以使用包扫描的方式,一次性的将所有mapper加载
逆向工程:自动生成Pojo类,还可以自动生成Mapper接口和映射文件
注意:生成的方式是追加而不是覆盖,所以不可以重复生成,重复生成的文件有问题. 如果想重复生成将原来生成的文件删除
概念区分:
- pojo:不按mvc分层,只是java bean有一些属性,还有get set方法
- domain:不按mvc分层,只是java bean有一些属性,还有get set方法
- po:用在持久层,还可以再增加或者修改的时候,从页面直接传入action中,它里面的java bean 类名等于表名,
属性名等于表的字段名,还有对应的get set方法- vo: view object表现层对象,主要用于在高级查询中从页面接收传过来的各种参数.好处是扩展性强
- bo: 用在servie层,现在企业基本不用.
这些po,vo, bo,pojo可以用在各种层面吗?
可以,也就是po用在表现层,vo用在持久层不报错,因为都是普通的java bean没有语法错误.
但是在企业最好不要混着用,因为这些都是设计的原则,混着用比较乱.不利于代码维护.