20160522--20160526----mybatis入门基础
一、基础知识:
1.对原生态jdbc程序(单独使用jdbc开发)问题总结
2.mybatis框架原理 (掌握)
3.mybatis入门程序
4.用户的增、删、改、查
5.SqlMapConfig.xml
6.输入映射
7.输出映射
8.动态sql
1.对原生态jdbc程序中问题总结
1.1 环境
java环境:jdk1.8.0_20
eclipse:luna
mysql:5.1
1.2 创建mysql数据
sql脚本:
/* SQLyog v10.2 MySQL - 5.1.72-community : Database - mybatis ********************************************************************* */ /*!40101 SET NAMES utf8 */; /*!40101 SET SQL_MODE=''*/; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; /*Table structure for table `items` */ CREATE TABLE `items` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL COMMENT '商品名称', `price` float(10,1) NOT NULL COMMENT '商品定价', `detail` text COMMENT '商品描述', `pic` varchar(64) DEFAULT NULL COMMENT '商品图片', `createtime` datetime NOT NULL COMMENT '生产日期', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; /*Table structure for table `orderdetail` */ CREATE TABLE `orderdetail` ( `id` int(11) NOT NULL AUTO_INCREMENT, `orders_id` int(11) NOT NULL COMMENT '订单id', `items_id` int(11) NOT NULL COMMENT '商品id', `items_num` int(11) DEFAULT NULL COMMENT '商品购买数量', PRIMARY KEY (`id`), KEY `FK_orderdetail_1` (`orders_id`), KEY `FK_orderdetail_2` (`items_id`), CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`orders_id`) REFERENCES `orders` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`items_id`) REFERENCES `items` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; /*Table structure for table `orders` */ CREATE TABLE `orders` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT '下单用户id', `number` varchar(32) NOT NULL COMMENT '订单号', `createtime` datetime NOT NULL COMMENT '创建订单时间', `note` varchar(100) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`), KEY `FK_orders_1` (`user_id`), CONSTRAINT `FK_orders_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; /*Table structure for table `user` */ CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(32) NOT NULL COMMENT '用户名称', `birthday` date DEFAULT NULL COMMENT '生日', `sex` char(1) DEFAULT NULL COMMENT '性别', `address` varchar(256) DEFAULT NULL COMMENT '地址', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
1.3 jdbc程序
使用jdbc查询mysql数据库中用户表的记录。
创建java工程,加入jar包:
数据库驱动包(mysql5.1)
上边的是mysql驱动。
下边的是oracle的驱动。
程序代码:
package com.dzq.mybatis.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * 通过Jdbc程序,总结单独的jdbc程序,总结其中 * * @author 小强 * */ public class JdbcTest { public static void main(String[] args) { // 数据库连接 Connection connection = null; // 预编译的Statement,可以提高数据库的性能 PreparedStatement preparedStatement = null; // 结果集对象 ResultSet resultSet = null; try { // 加载数据库驱动 Class.forName("com.mysql.jdbc.Driver"); // 通过驱动管理类获取数据库链接 connection = DriverManager .getConnection( "jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "mysql"); // 定义sql语句 ?表示占位符 String sql = "select * from user where username = ?"; // 获取预处理statement preparedStatement = connection.prepareStatement(sql); // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值 preparedStatement.setString(1, "王五"); // 向数据库发出sql执行查询,查询出结果集 resultSet = preparedStatement.executeQuery(); // 遍历查询结果集 while (resultSet.next()) { System.out.println(resultSet.getString("id") + " " + resultSet.getString("username")); } } catch (Exception e) { e.printStackTrace(); } finally { // 释放资源 if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (preparedStatement != null) { try { preparedStatement.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
1.4 问题总结
1、数据库连接,使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响 数据库性能。
设想:使用数据库连接池管理数据库连接。
2、将sql语句硬编码到java代码中,如果sql 语句修改,需要重新编译java代码,不利于系统维护。
设想:将sql语句配置在xml配置文件中,即使sql变化,不需要对java代码进行重新编译。
3、向preparedStatement中设置参数,对占位符号位置和设置参数值,硬编码在java代码中,不利于系统维护。
设想:将sql语句及占位符号和参数全部配置在xml中。
4、从resultSet中遍历结果集数据时,存在硬编码,将获取表的字段进行硬编码,,不利于系统维护。
设想:将查询的结果集,自动映射成java对象。
2.mybatis框架
2.1 mybatis是什么?
mybatis是一个持久层的框架,是apache下的顶级项目。
mybatis托管到goolecode下,再后来托管到github下(https://github.com/mybatis/mybatis-3/releases)。
mybatis让程序将主要精力放在sql上,通过mybatis提供的映射方式,自由灵活生成(半自动化,大部分需要程序员编写sql)满足需要sql语句。
mybatis可以将向 preparedStatement中的输入参数自动进行输入映射,将查询结果集灵活映射成java对象。(输出映射)
2.2 mybatis框架
3入门程序
3.1 需求
根据用户id(主键)查询用户信息
根据用户名称模糊查询用户信息
添加用户
删除 用户
更新用户
3.2 环境
java环境:jdk1.8.0_20
eclipse:luna
mysql:5.1
jar包:mybatis-3.2.7.jar
lib下:依赖包
mybatis-3.2.7.jar:核心 包
mybatis-3.2.7.pdf,操作指南
加入mysql的驱动包
3.3 log4j.properties
# Global logging configuration #\u5728\u5f00\u53d1\u73af\u5883\u4e0b\u65e5\u5fd7\u7ea7\u522b\u8981\u8bbe\u7f6e\u6210DEBUG\uff0c\u751f\u4ea7\u73af\u5883\u8bbe\u7f6e\u6210info\u6216error log4j.rootLogger=DEBUG, stdout # 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
3.4工程结构
3.5 SqlMapConfig.xml
配置mybatis的运行环境,数据源、事务等。
<?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> <!-- 和spring整合后 environments配置将废除--> <environments default="development"> <environment id="development"> <!-- 使用jdbc事务管理,事务控制由mybatis--> <transactionManager type="JDBC" /> <!-- 数据库连接池,由mybatis管理--> <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>
3.6根据用户id(主键)查询用户信息
3.6.1 创建po类
package com.dzq.mybatis.domain; import java.util.Date; public class User { // 属性名和数据库表的字段对应 private int id; private String username;// 用户姓名 private String sex;// 性别 private Date birthday;// 生日 private String address;// 地址 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
3.6.2 映射文件
映射文件命名:
User.xml(原始ibatis命名),mapper代理开发映射文件名称叫XXXMapper.xml,比如:UserMapper.xml、ItemsMapper.xml
在映射文件中配置sql语句。
<?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"> <!--命名空间,对sql进行分类管理,实现sql隔离 注意:使用mapper代理的方法开发,namespace就有特殊重要的作用 --> <mapper namespace="test"> <!-- 在映射文件中配置很多sql语句 --> <!-- 通过id查询用户表的记录 --> <!--通过select执行数据库的查询 id:标识映射文件中的sql,称为statemen的id 将sql语句封装到mappedstatement对象中 #{}:表示占位符 parameterType:指定输入参数类型,这里指定int型 {id}:其中的id表示接收输入参数,如果输入参数是简单类型,#{}中参数名可以任意,可以是value或者其他名称 resultType:指定sql输出结果所映射的java对象类型,select指定resultType将单条记录所映射成的java对象 --> <select id="findUserById" parameterType="int" resultType="com.dzq.mybatis.domain.User"> select * from user where id=#{id} </select> </mapper>
3.6.3 在SqlMapConfig.xml加载映射文件
在sqlMapConfig.xml中加载User.xml:
<!-- 加载 映射文件 --> <mappers> <mapper resource="sqlmap/User.xml"/> </mappers>
3.6.4 程序编写
package com.dzq.mybatis.first; import java.io.IOException; 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 com.dzq.mybatis.domain.User; public class MybatisFirst { // 根据id查询用户的信息,得到一条记录结果 public void findUserById(int id) throws IOException { //mybatis配置文件 String resource="SqlMapConfig.xml"; //得到配置文件流 InputStream inputStream=Resources.getResourceAsStream(resource); //创建会话工厂,传入mybatis的配置文件的信息 SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream); //通过工厂得到sqlsession SqlSession sqlSession=sqlSessionFactory.openSession(); //通过sqlsession操作数据库 //第一个参数:statement,映射文件中statement的id; 等于namespace+"."statement的id //第二个参数: parameter 指定和映射文件中parameter所匹配的parameterType的类型的 参数 //sqlSession.selectOne结果是与映射文件中所匹配的resultType类型对象 User user=sqlSession.selectOne("test.findUserById", 1); System.out.println(user); //释放资源 sqlSession.close(); } }
3.7 根据用户名称模糊查询用户信息
3.7.1 映射文件
使用User.xml,添加根据用户名称模糊查询用户信息的sql语句。
<!-- 根据用户名称模糊查询用户信息 --> <!-- resultType:单条记录所映射的java对象类型 ${}:拼接sql串,将接收到的参数不加任何修饰拼接到sql语句中 使用${}拼接sql,会引起sql注入 ${value}表示输入参数的内容,如果传入的类型是简单类型${}只能使用value --> <select id="findUserByUserName" parameterType="java.lang.String" resultType="com.dzq.mybatis.domain.User"> select * from user where username like '%${value}%' </select>
3.7.2 程序代码
//根据用户名称模糊查询用户信息 public void findUserByUserName() throws IOException{ //mybatis配置文件 String resource="SqlMapConfig.xml"; //得到配置文件流 InputStream inputStream=Resources.getResourceAsStream(resource); //创建会话工厂,传入mybatis的配置文件的信息 SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream); //通过工厂得到sqlsession SqlSession sqlSession=sqlSessionFactory.openSession(); //通过sqlsession操作数据库 //第一个参数:statement,映射文件中statement的id; 等于namespace+"."statement的id //第二个参数: parameter 指定和映射文件中parameter所匹配的parameterType的类型的 参数 //sqlSession.selectList结果是与映射文件中所匹配的resultType类型对象 List<User> list=sqlSession.selectList("test.findUserByUserName", "小明"); System.out.println(list); //释放资源 sqlSession.close(); }
3.8添加用户
3.8.1映射文件
在 User.xml中配置添加用户的Statement
<!-- 添加用户 --> <!-- parameterType:输入参数类型是pojo(包括用户信息) #{}:指定pojo的属性名,接收到pojo的属性值,mybatis通过ognl获取属性值 --> <insert id="addUser" parameterType="com.dzq.mybatis.domain.User"> insert into user(id,username,birthday,sex,address) value(#{id},#{username},#{birthday},#{sex},#{address}) </insert>
3.8.2程序代码
//添加用户信息 @Test public void addUser() throws IOException { // mybatis配置文件 String resource = "SqlMapConfig.xml"; // 得到配置文件流 InputStream inputStream = Resources.getResourceAsStream(resource); // 创建会话工厂,传入mybatis的配置文件的信息 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder() .build(inputStream); // 通过工厂得到sqlsession SqlSession sqlSession = sqlSessionFactory.openSession(); //插入用户对象 User user=new User(); user.setUsername("贱贱"); user.setSex("男"); user.setAddress("山东临沂"); user.setBirthday(new Date()); sqlSession.insert("test.addUser", user); //提交事务 sqlSession.commit(); // 释放资源 sqlSession.close(); }
3.8.3 自增主键返回
mysql自增主键,执行insert提交之前自动生成一个自增主键。
通过mysql函数获取到刚插入记录的自增主键:
LAST_INSERT_ID()
是insert之后调用此函数。
修改insertUser定义:
<insert id="addUser" parameterType="com.dzq.mybatis.domain.User"> <!-- insert 插入数据返回到User对象中 select last_insert_id:得到刚插入进去数据的主键值,只适用于自增主键 keyProperty:将要查询到的主键值设置到parameterType对象的那个属性 order:相对于insert语句的执行顺序 --> <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"> select last_insert_id() </selectKey> insert into user(username,birthday,sex,address) value(#{username},#{birthday},#{sex},#{address}) </insert>
3.8.14 非自增主键返回(使用uuid())
使用mysql的uuid()函数生成主键,需要修改表中id字段类型为string,长度设置成35位。
执行思路:
先通过uuid()查询到主键,将主键输入 到sql语句中。
执行uuid()语句顺序相对于insert语句之前执行。
通过oracle的序列生成主键:
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User"> <selectKey resultType="java.lang.Integer" order="BEFORE" keyProperty="id"> SELECT 自定义序列.NEXTVAL FROM DUAL </selectKey> insert into user(id,username,birthday,sex,address) values(#{id},#{username},#{birthday},#{sex},#{address}) </insert>
注意这里使用的order是“BEFORE”
3.9删除用户
3.9.1映射文件
<!-- 删除用户 根据id删除用户,需要输入id值 --> <delete id="deleteUser" parameterType="int" > delete from user where id=#{id} </delete>
3.9.2程序代码
//删除用户信息 @Test public void deleteUser() throws IOException { // mybatis配置文件 String resource = "SqlMapConfig.xml"; // 得到配置文件流 InputStream inputStream = Resources.getResourceAsStream(resource); // 创建会话工厂,传入mybatis的配置文件的信息 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder() .build(inputStream); // 通过工厂得到sqlsession SqlSession sqlSession = sqlSessionFactory.openSession(); //传入id,根据id删除用户 sqlSession.delete("test.deleteUser", 33); //提交事务 sqlSession.commit(); // 释放资源 sqlSession.close(); }
3.10更新用户
3.10.1映射文件
<!-- 更新用户 分析:需要传入用户的id 用户的更新信息 parameterType,指定user对象,包括id和更新信息,注意,id必须存在 #{id}接收user里的属性值 --> <update id="updateUser" parameterType="com.dzq.mybatis.domain.User"> update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id} </update>
3.10.2程序代码
// 更新用户信息 @Test public void updateUser() throws IOException { // mybatis配置文件 String resource = "SqlMapConfig.xml"; // 得到配置文件流 InputStream inputStream = Resources.getResourceAsStream(resource); // 创建会话工厂,传入mybatis的配置文件的信息 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder() .build(inputStream); // 通过工厂得到sqlsession SqlSession sqlSession = sqlSessionFactory.openSession(); User user = new User(); user.setId(32); user.setUsername("贱贱贱"); user.setSex("女"); user.setAddress("山东莱芜"); user.setBirthday(new Date()); // 传入user对象,根据id更新用户 sqlSession.update("test.updateUser", user); // 提交事务 sqlSession.commit(); // 释放资源 sqlSession.close(); }
3.11小结
3.11.1 #{}和${}
#{}表示一个占位符号,#{}接收输入参数,类型可以是简单类型,pojo、hashmap。
如果接收简单类型,#{}中可以写成value或其它名称。
#{}接收pojo对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性...的方式获取对象属性值。
${}表示一个拼接符号,会引用sql注入,所以不建议使用${}。
${}接收输入参数,类型可以是简单类型,pojo、hashmap。
如果接收简单类型,${}中只能写成value。
${}接收pojo对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性...的方式获取对象属性值。
3.11.2 parameterType和resultType
parameterType:指定输入参数类型,mybatis通过ognl从输入对象中获取参数值拼接在sql中。
resultType:指定输出结果类型,mybatis将sql查询结果的一行记录数据映射为resultType指定类型的对象。
3.11.3 selectOne和selectList
selectOne查询一条记录,如果使用selectOne查询多条记录则抛出异常:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:70)
selectList可以查询一条或多条记录。
3.12 mybatis和hibernate本质区别和应用场景
hibernate:是一个标准ORM框架(对象关系映射)。入门门槛较高的,不需要程序写sql,sql语句自动生成了。
对sql语句进行优化、修改比较困难的。
应用场景:
适用与需求变化不多的中小型项目,比如:后台管理系统,erp、orm、oa。。
mybatis:专注是sql本身,需要程序员自己编写sql语句,sql修改、优化比较方便。mybatis是一个不完全 的ORM框架,虽然程序员自己写sql,mybatis 也可以实现映射(输入映射、输出映射)。
应用场景:
适用与需求变化较多的项目,比如:互联网项目。
企业进行技术选型,以低成本 高回报作为技术选型的原则,根据项目组的技术力量进行选择。
4 mybatis开发dao的方法
4.1 SqlSession使用范围
4.1.1 SqlSessionFactoryBuilder
通过SqlSessionFactoryBuilder创建会话工厂SqlSessionFactory
将SqlSessionFactoryBuilder当成一个工具类使用即可,不需要使用单例管理SqlSessionFactoryBuilder。
在需要创建SqlSessionFactory时候,只需要new一次SqlSessionFactoryBuilder即可。
4.1.2 SqlSessionFactory
通过SqlSessionFactory创建SqlSession,使用单例模式管理sqlSessionFactory(工厂一旦创建,使用一个实例)。
将来mybatis和spring整合后,使用单例模式管理sqlSessionFactory。
4.1.3 SqlSession
SqlSession是一个面向用户(程序员)的接口。
SqlSession中提供了很多操作数据库的方法:如:selectOne(返回单个对象)、selectList(返回单个或多个对象)、。
SqlSession是线程不安全的,在SqlSesion实现类中除了有接口中的方法(操作数据库的方法)还有数据域属性。
SqlSession最佳应用场合在方法体内,定义成局部变量使用。
4.2 原始dao开发方法(程序员需要写dao接口和dao实现类)
4.2.1 思路
程序员需要写dao接口和dao实现类。
需要向dao实现类中注入SqlSessionFactory,在方法体内通过SqlSessionFactory创建SqlSession
4.2.2 dao接口
package com.dzq.mybatis.dao; import com.dzq.mybatis.domain.User; public interface UserDao { // 根据id查询用户信息 public User findUserById(int id) throws Exception; // 添加用户 public void addUser(User user) throws Exception; // 删除用户 public void deleteUser(int id) throws Exception; // 修改用户信息 public void updateUser(User user) throws Exception; }
4.2.2 dao接口实现类
package com.dzq.mybatis.dao.impl; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import com.dzq.mybatis.dao.UserDao; import com.dzq.mybatis.domain.User; public class UserDaoImpl implements UserDao { // 需要向dao实现类中注入SqlSessionFactory // 这里通过构造方法注入 private SqlSessionFactory sqlSessionFactory; public UserDaoImpl(SqlSessionFactory sqlSessionFactory) { this.sqlSessionFactory = sqlSessionFactory; } @Override public User findUserById(int id) throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); User user = sqlSession.selectOne("test.findUserById", id); // 释放资源 sqlSession.close(); return user; } @Override public void addUser(User user) throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 执行插入 sqlSession.insert("test.addUser", user); // 提交事务 sqlSession.commit(); // 释放资源 sqlSession.close(); } @Override public void deleteUser(int id) throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); sqlSession.delete("test.deleteUser", id); // 提交事务 sqlSession.commit(); // 释放资源 sqlSession.close(); } @Override public void updateUser(User user) throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); sqlSession.update("test.updateUser", user); // 提交事务 sqlSession.commit(); // 释放资源 sqlSession.close(); } }
4.2.4 测试代码:
package com.dzq.mybatis.test; import java.io.InputStream; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import com.dzq.mybatis.dao.UserDao; import com.dzq.mybatis.dao.impl.UserDaoImpl; import com.dzq.mybatis.domain.User; public class UserDaoImplTest { private SqlSessionFactory sqlSessionFactory; @Before public void setUp() throws Exception { // 创建sqlSessionFactory // mybatis配置文件 String resource = "SqlMapConfig.xml"; // 得到配置文件流 InputStream inputStream = Resources.getResourceAsStream(resource); // 创建会话工厂,传入mybatis的配置文件的信息 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void testFindUserById() throws Exception { // 创建UserDao对象 UserDao userdao = new UserDaoImpl(sqlSessionFactory); // 调用userDao方法 User user=userdao.findUserById(1); System.out.println(user.getUsername()); } }
4.2.5 总结原始 dao开发问题
1、dao接口实现类方法中存在大量模板方法,设想能否将这些代码提取出来,大大减轻程序员的工作量。
2、调用sqlsession方法时将statement的id硬编码了
3、调用sqlsession方法时传入的变量,由于sqlsession方法使用泛型,即使变量类型传入错误,在编译阶段也不报错,不利于程序员开发。
4.3 mapper代理方法(程序员只需要mapper接口(相当 于dao接口))
4.3.1 思路(mapper代理开发规范)
程序员还需要编写mapper.xml映射文件
程序员编写mapper接口需要遵循一些开发规范,mybatis可以自动生成mapper接口实现类代理对象。
开发规范:
1、在mapper.xml中namespace等于mapper接口地址
<!--命名空间,对sql进行分类管理,实现sql隔离 注意:使用mapper代理的方法开发,namespace就有特殊重要的作用 namespace等于mapper接口地址 --> <mapper namespace="com.dzq.mybatis.mapper.UserMapper">
2、mapper.java接口中的方法名和mapper.xml中statement的id一致
3、mapper.java接口中的方法输入参数类型和mapper.xml中statement的parameterType指定的类型一致。
4、mapper.java接口中的方法返回值类型和mapper.xml中statement的resultType指定的类型一致。
<select id="findUserById" parameterType="int" resultType="com.dzq.mybatis.domain.User"> select * from user where id=#{id} </select>
// 根据id查询用户信息 public User findUserById(int id) throws Exception;
总结:
以上开发规范主要是对下边的代码进行统一生成:
User user = sqlSession.selectOne("test.findUserById", id);
sqlSession.insert("test.insertUser", user);
。。。。
4.3.2 mapper.java
package com.dzq.mybatis.mapper; import java.util.List; import com.dzq.mybatis.domain.User; public interface UserMapper { public User findUserById(int id) throws Exception; }
4.3.3 mapper.xml
<select id="findUserById" parameterType="int" resultType="com.dzq.mybatis.domain.User"> select * from user where id=#{id} </select>
4.3.4 在SqlMapConfig.xml中加载mapper.xml
<!-- 加载 映射文件 --> <mappers> <mapper resource="sqlmap/User.xml"/> <mapper resource="mapper/UserMapper.xml"/> </mappers>
4.3.5 测试
package com.dzq.mybatis.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.junit.Before; import org.junit.Test; import com.dzq.mybatis.domain.User; import com.dzq.mybatis.mapper.UserMapper; public class UserMapperTest { private SqlSessionFactory sqlSessionFactory; @Before public void setUp() throws Exception { // 创建sqlSessionFactory // mybatis配置文件 String resource = "SqlMapConfig.xml"; // 得到配置文件流 InputStream inputStream = Resources.getResourceAsStream(resource); // 创建会话工厂,传入mybatis的配置文件的信息 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void testFindUserById() throws Exception { SqlSession sqlSession=sqlSessionFactory.openSession(); //创建一个usermapper的对象 UserMapper userMapper=sqlSession.getMapper(UserMapper.class); //调用userMapper方法 User user=userMapper.findUserById(1); System.out.println(user.getUsername()); } }
4.3.6 整个接口
package com.dzq.mybatis.mapper; import java.util.List; import com.dzq.mybatis.domain.User; public interface UserMapper { // 根据id查询用户信息 public User findUserById(int id) throws Exception; // 添加用户 public void addUser(User user) throws Exception; // 删除用户 public void deleteUser(int id) throws Exception; // 根据用户名称查询用户列表 public List<User> findUserByUserName(String username) throws Exception; }
4.3.7 一些问题总结
4.3.7.1 代理对象内部调用selectOne或selectList
如果mapper方法返回单个pojo对象(非集合对象),代理对象内部通过selectOne查询数据库。
如果mapper方法返回集合对象,代理对象内部通过selectList查询数据库。
4.3.7.2 mapper接口方法参数只能有一个是否影响系统 开发
mapper接口方法参数只能有一个,系统是否不利于扩展维护。
系统 框架中,dao层的代码是被业务层公用的。
即使mapper接口只有一个参数,可以使用包装类型的pojo满足不同的业务方法的需求。
注意:持久层方法的参数可以包装类型、map。。。,service方法中建议不要使用包装类型(不利于业务层的可扩展)。
5 SqlMapConfig.xml
mybatis的全局配置文件SqlMapConfig.xml,配置内容如下:
properties(属性)
settings(全局配置参数)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)
5.1 properties属性
需求:
将数据库连接参数单独配置在db.properties中,只需要在SqlMapConfig.xml中加载db.properties的属性值。
在SqlMapConfig.xml中就不需要对数据库连接参数硬编码。
将数据库连接参数只配置在db.properties中,原因:方便对参数进行统一管理,其它xml可以引用该db.properties。
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis jdbc.username=root jdbc.password=
<!-- 加载属性文件 --> <properties resource="db.properties"> <!--properties中还可以配置一些属性名和属性值 --> <!-- <property name="jdbc.driver" value=""/> --> </properties>
properties特性:
注意: MyBatis 将按照下面的顺序来加载属性:
在 properties 元素体内定义的属性首先被读取。
然后会读取properties 元素中resource或 url 加载的属性,它会覆盖已读取的同名属性。
最后读取parameterType传递的属性,它会覆盖已读取的同名属性。
建议:
不要在properties元素体内添加任何属性值,只将属性值定义在properties文件中。
在properties文件中定义属性名要有一定的特殊性,如:XXXXX.XXXXX.XXXX
5.2 settings全局参数配置
mybatis框架在运行时可以调整一些运行参数。
比如:开启二级缓存、开启延迟加载。。
全局参数将会影响mybatis的运行行为。
5.3 typeAliases(别名)重点
5.3.1 需求
在mapper.xml中,定义很多的statement,statement需要parameterType指定输入参数的类型、需要resultType指定输出结果的映射类型。
如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名,在mapper.xml中通过别名定义,方便开发。
5.3.2 mybatis默认支持别名
5.3.3 自定义别名
5.3.3.1 单个别名定义
<!-- 别名定义 --> <typeAliases> <!-- 针对单个别名的定义 type:类型路径 alias:别名 --> <typeAlias type="com.dzq.mybatis.domain.User" alias="user"/> </typeAliases>
引用别名:
<select id="findUserById" parameterType="int" resultType="user"> select * from user where id=#{id} </select>
5.3.3.2 批量定义别名(常用)
<!-- 批量别名定义 指定包名,mybatis自动扫描domain类,自动定义别名,别名就是类名 --> <package name="com.dzq.mybatis.domain"/>
5.4 typeHandlers(类型处理器)
mybatis中通过typeHandlers完成jdbc类型和java类型的转换。
通常情况下,mybatis提供的类型处理器满足日常需要,不需要自定义.
mybatis支持类型处理器:
5.5 mappers(映射配置)
5.5.1 通过resource加载单个映射文件
<!-- 加载 映射文件 --> <mappers> <mapper resource="sqlmap/User.xml" /> <mapper resource="mapper/UserMapper.xml" /> </mappers>
5.5.2 通过mapper接口加载单个mapper
<!--通过mapper接口加载单个mapper 遵循一些规范:需要将mapper接口的类名和mapper.xml映射文件名称保持一致,且在一个目录 上边规范的前提是:你使用的是mapper代理的方法 --> <mapper class="com.dzq.mybatis.mapper.UserMapper"/>
需要将mapper接口的类名和mapper.xml映射文件名称保持一致,且在一个目录
5.5.3 批量加载mapper(推荐使用)
<!--批量加载mapper(推荐使用) 指定mapper接口的包名,mybatis自动扫描包下所有mapper接口进行加载 遵循一些规范:需要将mapper接口的类名和mapper.xml映射文件名称保持一致,且在一个目录 上边规范的前提是:你使用的是mapper代理的方法 --> <package name="com.dzq.mybatis.mapper"/>
6 输入映射
通过parameterType指定输入参数的类型,类型可以是简单类型、hashmap、pojo的包装类型。
6.1 传递pojo的包装对象
6.1.1 需求
完成用户信息的综合查询,需要传入查询条件很复杂(可能包括用户信息、其它信息,比如商品、订单的)
6.1.2 定义包装类型pojo
针对上边需求,建议使用自定义的包装类型的pojo。
在包装类型的pojo中将复杂的查询条件包装进去。
package com.dzq.mybatis.domain; public class UserQueryVo { //这里包装所需要的查询条件 //用户查询条件 private UserCustom userCustom; //还可以包装其他的查询条件 商品、订单 public UserCustom getUserCustom() { return userCustom; } public void setUserCustom(UserCustom userCustom) { this.userCustom = userCustom; } }
6.1.3 mapper.xml
在UserMapper.xml中定义用户信息综合查询(查询条件复杂,通过高级查询进行复杂关联查询)。
<!-- 用户信息的综合查询 #{userCustom.sex}:取出包装类型的性别信息 ${userCustom.username}:取出包装类中的用户名 --> <select id="findUserList" parameterType="com.dzq.mybatis.domain.UserQueryVo" resultType="com.dzq.mybatis.domain.UserCustom"> select * from user where user.sex=#{userCustom.sex} and user.username like '%${userCustom.username}%' </select>
6.1.4 mapper.java
//用户信息综合查询 public List<UserCustom> findUserList(UserQueryVo userQueryVo) throws Exception;
6.1.5 测试代码
@Test public void testFindUserList() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 创建一个usermapper的对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //创建包装对象,设置查询条件 UserQueryVo userQueryVo=new UserQueryVo(); UserCustom userCustom=new UserCustom(); userCustom.setSex("1"); userCustom.setUsername("小明"); userQueryVo.setUserCustom(userCustom); // 调用userMapper方法 List<UserCustom> list = userMapper.findUserList(userQueryVo); //sqlSession.close(); System.out.println(list); }
7 输出映射
7.1 resultType
使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。
如果查询出来的列名和pojo中的属性名全部不一致,没有创建pojo对象。
只要查询出来的列名和pojo中的属性有一个一致,就会创建pojo对象,不一致的属性的值为null。
7.1.1 输出简单类型
7.1.1.1 需求
用户信息的综合查询列表总数,通过查询总数和上边用户综合查询列表才可以实现分页。
7.1.1.2 mapper.xml
<!-- 用户信息综合查询总数 parameterType:输入类型和findUserList一致 resultType:输出结果类型为整型 --> <select id="findUserCount" parameterType="com.dzq.mybatis.domain.UserQueryVo" resultType="int"> select count(*) from user where user.sex=#{userCustom.sex} and user.username like '%${userCustom.username}%' </select>
7.1.1.3 mapper.java
//用户信息综合查询总数 public int findUserCount(UserQueryVo userQueryVo) throws Exception;
7.1.1.4 测试代码
@Test public void testFindUserCount() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 创建一个usermapper的对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //创建包装对象,设置查询条件 UserQueryVo userQueryVo=new UserQueryVo(); UserCustom userCustom=new UserCustom(); userCustom.setSex("1"); userCustom.setUsername("小明"); userQueryVo.setUserCustom(userCustom); // 调用userMapper方法 int count = userMapper.findUserCount(userQueryVo); System.out.println(count); }
7.1.1.5 小结
查询出来的结果集只有一行且一列,可以使用简单类型进行输出映射。
7.1.2 输出pojo对象和pojo列表
不管是输出的pojo单个对象还是一个列表(list中包括pojo),在mapper.xml中resultType指定的类型是一样的。
在mapper.java指定的方法返回值类型不一样:
1、输出单个pojo对象,方法返回值是单个对象类型
// 根据id查询用户信息 public User findUserById(int id) throws Exception;
2、输出pojo对象list,方法返回值是List<Pojo>
// 根据用户名称查询用户列表 public List<User> findUserByUserName(String username) throws Exception;
生成的动态代理对象中是根据mapper方法的返回值类型确定是调用selectOne(返回单个对象调用)还是selectList (返回集合对象调用 ).
7.2 resultMap
mybatis中使用resultMap完成高级输出结果映射。(一对一、一对多、多对多)(小入门)
7.2.1 resultMap使用方法
如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。
1、定义resultMap
2、使用resultMap作为statement的输出映射类型
7.2.2 将下边的sql使用User完成映射
SELECT id id_,username username_ FROM USER WHERE id=#{value}
User类中属性名和上边查询列名不一致。
7.2.2.1 定义reusltMap
<!-- 定义resultMap 将 select id id_,username username_ from user where id=#{id}查询和User做一个映射 type:resultMap最终映射的java对象类型,可以使用别名 id:对resultMap的唯一标识 --> <resultMap type="user" id="userResultMap"> <!-- id表示查询结果集中唯一的标识 column:查询出来的列名 property:type中指定的pojo中的属性名 最终resultMap对column和property做一个映射关系(对应关系) --> <id column="id_" property="id"/> <!-- 对普通列的定义 column:查询出来的列名 property:type中指定的pojo中的属性名 最终resultMap对column和property做一个映射关系(对应关系) --> <result column="username_" property="username"/> </resultMap>
7.2.2.2 使用resultMap作为statement的输出映射类型
<!-- 使用resultMap来进行输出的映射 resultMap:指定定义的resultMap的id,如果resultMap在其他映射文件中,前边需要加上namespace --> <select id="findUserByIdResultMap" parameterType="int" resultMap="userResultMap"> select id id_,username username_ from user where id=#{id} </select>
7.2.2.3 mapper.java
//根据id查询用户信息,使用resultMap输出 public User findUserByIdResultMap(int id)throws Exception;
7.2.2.4 测试
@Test public void testFindUserByIdResultMap() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 创建一个usermapper的对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 调用userMapper方法 User user = userMapper.findUserByIdResultMap(1); System.out.println(user.getUsername()); }
7.3 小结
使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。
如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。
8 动态sql
8.1 什么是动态sql
mybatis核心 对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接、组装。
8.2 需求
用户信息综合查询列表和用户信息查询列表总数这两个statement的定义使用动态sql。
对查询条件进行判断,如果输入参数不为空才进行查询条件拼接。
8.3 mapper.xml
<!-- 用户信息的综合查询 #{userCustom.sex}:取出包装类型的性别信息 ${userCustom.username}:取出包装类中的用户名 --> <select id="findUserList" parameterType="com.dzq.mybatis.domain.UserQueryVo" resultType="com.dzq.mybatis.domain.UserCustom"> select * from user <!-- where 可以自动的去掉条件中的第一个and --> <where> <if test="userCustom!=null"> <if test="userCustom.sex!=null and userCustom.sex!=''"> and user.sex=#{userCustom.sex} </if> <if test="userCustom.username!=null and userCustom.username!=''"> and user.username like '%${userCustom.username}%' </if> </if> </where> </select> <!-- 用户信息综合查询总数 parameterType:输入类型和findUserList一致 resultType:输出结果类型为整型 --> <select id="findUserCount" parameterType="com.dzq.mybatis.domain.UserQueryVo" resultType="int"> select count(*) from user <!-- where 可以自动的去掉条件中的第一个and --> <where> <if test="userCustom!=null"> <if test="userCustom.sex!=null and userCustom.sex!=''"> and user.sex=#{userCustom.sex} </if> <if test="userCustom.username!=null and userCustom.username!=''"> and user.username like '%${userCustom.username}%' </if> </if> </where> </select>
8.4 测试代码
public void testFindUserList() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 创建一个usermapper的对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //创建包装对象,设置查询条件 UserQueryVo userQueryVo=new UserQueryVo(); UserCustom userCustom=new UserCustom(); //由于使用了动态sql,如果不设置某个值,这个条件不会拼接到sql中 //userCustom.setSex("1"); userCustom.setUsername("小明"); userQueryVo.setUserCustom(userCustom); // 调用userMapper方法 List<UserCustom> list = userMapper.findUserList(userQueryVo); //sqlSession.close(); System.out.println(list); }
8.5 sql片段
8.5.1 需求
将上边实现的动态sql判断代码块抽取出来,组成一个sql片段。其它的statement中就可以引用sql片段。
方便程序员进行开发。
8.5.2 定义sql片段
!--定义sql片段 id:sql片段的唯一标识 经验:基于单表定义sql片段,这样可重用性才高 sql片段中不要包括where --> <sql id="query_user_where"> <if test="userCustom!=null"> <if test="userCustom.sex!=null and userCustom.sex!=''"> and user.sex=#{userCustom.sex} </if> <if test="userCustom.username!=null and userCustom.username!=''"> and user.username like '%${userCustom.username}%' </if> </if> </sql>
8.5.3 引用sql片段
在mapper.xml中定义的statement中引用sql片段:
<!-- 用户信息的综合查询 #{userCustom.sex}:取出包装类型的性别信息 ${userCustom.username}:取出包装类中的用户名 --> <select id="findUserList" parameterType="com.dzq.mybatis.domain.UserQueryVo" resultType="com.dzq.mybatis.domain.UserCustom"> select * from user <!-- where 可以自动的去掉条件中的第一个and --> <where> <!-- 这就是引用sql片段的id 如果refid不在本mapper中,需要加上namespace--> <include refid="query_user_where"> </include> <!-- 在这里还会引用其他sql片段,商品等 --> </where> </select>
<!-- 用户信息综合查询总数 parameterType:输入类型和findUserList一致 resultType:输出结果类型为整型 --> <select id="findUserCount" parameterType="com.dzq.mybatis.domain.UserQueryVo" resultType="int"> select count(*) from user <!-- where 可以自动的去掉条件中的第一个and --> <where> <!-- 这就是引用sql片段的id 如果refid不在本mapper中,需要加上namespace--> <include refid="query_user_where"> </include> <!-- 在这里还会引用其他sql片段,商品等 --> </where> </select>
8.6 foreach
向sql传递数组或List,mybatis使用foreach解析
8.6.1 需求
在用户查询列表和查询总数的statement中增加多个id输入查询。
sql语句如下:
两种方法:
SELECT * FROM USER WHERE id=1 OR id=10 OR id=16
SELECT * FROM USER WHERE id IN(1,10,16)
8.6.2 在输入参数类型中添加List<Integer> ids传入多个id
//传入多个id private List<Integer> ids; public List<Integer> getIds() { return ids; } public void setIds(List<Integer> ids) { this.ids = ids; }
8.6.3 修改mapper.xml
WHERE id=1 OR id=10 OR id=16
在查询条件中,查询条件定义成一个sql片段,需要修改sql片段。
<if test="ids!=null"> <!-- 使用foreach遍历我们传入的ids collection:指定输入对象中集合属性 item:每次遍历生成对象名 open:开始遍历时要拼接的串 close:结束遍历时拼接的串 separator:遍历的两个对象中间所要拼接的串 --> <!-- 使用实现下边的sql拼接 and (id=1 or id=10 or id=16) --> <foreach collection="ids" item="user_id" open="and (" close=")" separator="or"> <!-- 每次遍历所需要拼接的串--> id=#{user_id} </foreach> </if>
8.6.4 测试代码
@Test public void testFindUserList() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 创建一个usermapper的对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //创建包装对象,设置查询条件 UserQueryVo userQueryVo=new UserQueryVo(); UserCustom userCustom=new UserCustom(); //由于使用了动态sql,如果不设置某个值,这个条件不会拼接到sql中 userCustom.setSex("1"); userCustom.setUsername("小明"); //传入多个id List <Integer> ids=new ArrayList<Integer>(); ids.add(1); ids.add(10); ids.add(16); userQueryVo.setUserCustom(userCustom); //将ids传入statement中 userQueryVo.setIds(ids); // 调用userMapper方法 List<UserCustom> list = userMapper.findUserList(userQueryVo); //sqlSession.close(); System.out.println(list.get(0).getUsername()); }
8.6.5 另外一个sql的实现:
<!--实现 and id in(1,10,16)拼接 --> <foreach collection="ids" item="user_id" open=" and id in(" close=")" separator=","> <!-- 每次遍历所需要拼接的串--> #{user_id} </foreach>