MybatisDay01(基础配置 自定义sql片段、resultMap使用、动态sql)

mybatis
基础知识
对原生态jdbc程序(单独使用jdbc开发)问题总结
mybatis框架原理

mybatis入门程序
	用户的增、删、改、查
mybatis开发dao两种方法
	原始dao开发方法(程序需要编写dao接口和dao实现类)
	mybatis的mapper接口(相当于dao接口)代理开发方法

mybatis配置文件SqlMapConfig.xml
mybatis核心
	mybatis输入映射
	mybatis输出映射

mybatis的动态sql

高级知识
	订单商品数据模型分析
	高级结果集映射(一对一、一对多、多对多)
	mybatis延迟加载
	mybatis查询缓存(一级缓存、二级缓存)
	mybatis和spring进行整合
	mybatis逆向工程

1、对原生态jdbc程序中问题总结
1.1、环境
java环境:jdk1.8
eclipse:oxygen
mysql:5.5
1.2、创建mysql数据
导入sql脚本
sql_table.sql:记录表结构
sql_data.sql:记录测试数据,在实际企业开发中,最后提供一个初始化数据脚本

user表

1.3、jdbc程序
使用jdbc查询mysql数据库中用户表的记录
在导入相关类的时候使用java.sql开头的接口 因为以后可能要用oracle的所以java.sql泛用性强
创建java工程,加入jar包

数据库驱动包(mysql5.5)
mysql-connector-java-5.1.5.jar
ojdbc6.jar

上边是mysql驱动
下边是oracle的驱动

JdbcConnection

public class JdbcConnection {
	public static void main(String[] args) {
		//数据库连接
		Connection connection = null;
		//预编译的Statement,使用预编译的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","123");
			
			//定义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) {
					
					e.printStackTrace();
				} finally {
					resultSet = null;
				}
			}
			if(preparedStatement != null) {
				try {
					preparedStatement.close();
				} catch (SQLException e) {
					
					e.printStackTrace();
				}finally {
					preparedStatement = null;
				}
			}
			if (connection != null ) {
				try {
					connection.close();
				} catch (SQLException e) {
					
					e.printStackTrace();
				} finally {
					connection = null;
				}
			}
		}
	}
}
使用jdbc的原始方法(未经封装)实现了查询数据库表记录的操作

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托管到googlecode下,再后来脱管到github下(https://github.com/mybatis/mybatis-3/releases)。

在这里插入图片描述

	mybatis-3.4.6.jar---mybatis的核心包
	lib---mybatis的依赖包
	mybatis-3.4.6.pdf---mybatis使用手册
	
	mybatis让程序将主要精力放在sql上,通过mybatis提供的映射方式,自由灵活生成(半自动化,大部分需要程序员编写sql)满足需要sql语句

	mybatis可以将向preparedStatement中的输入参数自动进行输入映射,将查询结果集灵活映射成java对象(输出映射)

2.2、mybatis框架

在这里插入图片描述

3、入门程序
3.1、需求
	根据用户id(主键)查询用户信息
	根据用户名称模糊查询用户信息
	添加用户
	删除用户
	更新用户

3.2、环境
	java环境jdk1.8
	eclipse:oxygen
	mysql:5.5
	mybatis运行环境(jar包)
	从https://github.com/mybatis/mybatis-3/releases下载
	加入mysql驱动包
3.3、log4j.properties
	#Global logging configuration
	#在开发环境下日志级别要设置成DEBUG 如果设置成info不会报出错误信息而是一般要显示的信息比如登录登出
	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
	mybatis默认用log4j

log4j.properties详解

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="com.mysql.jdbc.driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123"/>
            </dataSource>
        </environment>
    </environments>
</configuration>
SqlMapConfig.xml是mybatis核心配置文件,上边文件的配置内容为数据源、事务管理

3.6、根据用户id(主键)查询用户信息
3.6.1、创建po类
public class User {
	//属性名和数据库表的字段相对应
	private int id ;
	private String username;//用户姓名
	private String sex;//性别
	private Date birthday;//生日
	private String 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">
	<!-- namespace命名空间,作用就是对sql进行分类化管理,理解为sql隔离 
		使用mapper代理方法开发,namespace有特殊重要的作用
	-->

	<mapper namespace="test">
		<!-- 在映射文件中配置很多sql语句
			需求:通过id查询用户表的记录
			通过select执行数据库查询
			id:标识映射文件中的sql
			将sql语句封装到mappedStatement对象中,所以将id称为statement的id
		 	#{}表示一个占位符号
		 	#{}:其中的id表示接入输入的参数,参数名称就是id,如果输入参数是简单类型,#{}中的参数名可以任意,可以value或者其他名称
		 	resultType:指定sql输出结果所映射的java对象类型,select指定resultType表示将单条记录映射成的java对象
		 -->
		 <select id="findUserById" parameterType="int" resultType="my.mybatis.po.User">
		 	SELECT * FROM USER WHERE username=#{value}
		 </select>
	</mapper>

3.6.3在SqlMapConfig.xml中加载映射文件
    <!-- 加载映射文件 -->
    <mappers>
    	<mapper resource="sqlmap/User.xml"/>
    </mappers>

3.6.4、程序编写
 	//根据id查询用户信息,得到一条记录结果
	@Test
	public void findUserByIdTest() throws IOException {
		//mybatis配置文件
		String resource = "SqlMapConfig.xml";
		InputStream stream = Resources.getResourceAsStream(resource);
		//创建会话工厂,传入mybatis的配置文件信息
		SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(stream);
		
		//通过工厂得到SqlSession
		SqlSession sqlSession = sessionFactory.openSession();
		//通过SqlSession操作数据库
		//第一个参数:映射文件中statement的id,等于=namespace+"."+statement的id
		//第二个参数:指定和映射文件中锁匹配的parameterType类型的参数
		//sqlSession.selectOne结果是与映射文件中所匹配的resultType类型的对象
		User user   = sqlSession.selectOne("test.findUserById", 1);
		System.out.println(user);
		sqlSession.close();
	}

3.7、根据用户名称模糊查询用户
3.7.1、映射文件
	 <!-- 根据用户名称模糊查询用户信息,可能返回多条
	 resulType:指定就是单条记录所映射的java对象类型
	 ${}:表示拼接sql串,将接受到参数的内容不加任何修饰拼接在sql中
	 使用${}拼接sql,引起sql注入  因为可以添加任意字符所以会有特殊的sql被插入进去
	 ${value}:接受输入参数的内容,如果传入类型是简单类型,${}只能使用value
	 	
	  -->
	 <select id="findUserByName" parameterType="java.lang.String" resultType="my.mybatis.po.User">
	 	SELECT * FROM USER WHERE username like '%${value}%'
	 </select>

3.7.2、程序代码
	//查询用户名称模糊查询用户信息
	@Test
	public void findUserByUsername() throws IOException {
		//mybatis配置文件
		String resource = "SqlMapConfig.xml";
		InputStream stream = Resources.getResourceAsStream(resource);
		//创建会话工厂,传入mybatis的配置文件信息
		SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(stream);
		
		//通过工厂得到SqlSession
		SqlSession sqlSession = sessionFactory.openSession();
		List<User> selectList = sqlSession.selectList("test.findUserByName", "小");
		System.out.println(selectList.toString());
	}

3.8、添加用户
3.8.1、映射文件
	在User.xml
	 <!-- 添加用户
	 	parameterType:指定输入参数是pojo(包括用户信息)
	 	#{}中指定pojo的属性名,接收到pojo对象的属性值 ,mybatis通过OGNL获取对象的属性值
	  -->
	<insert id="insertUser" parameterType="my.mybatis.po.User">
		insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address});
	</insert>
3.8.2、程序代码
	//插入用户
	@Test
	public void insertUser() throws IOException, ParseException {
		// mybatis配置文件
		String resource = "SqlMapConfig.xml";
		InputStream stream = Resources.getResourceAsStream(resource);
		// 创建会话工厂,传入mybatis的配置文件信息
		SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(stream);

		// 通过工厂得到SqlSession
		SqlSession sqlSession = sessionFactory.openSession();
		//插入用户对象
		User aUser = new User();
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
		Date parse = simpleDateFormat.parse("1921-10-21");
		aUser.setSex("男");
		aUser.setAddress("嗖嗖嗖");
		aUser.setBirthday(parse);
		aUser.setUsername("小采");
		//list中的user和映射文件中resultType所指定的类型一致
		
		sqlSession.insert("test.insertUser", aUser);
		//提交事务
		sqlSession.commit();
		
		//获取用户信息主键
		System.out.println(aUser.getId());
		
		//关闭会话
		sqlSession.close();
	}
3.8.3、自增主键返回
	mysql自增主键,执行insert提交之前自动生成一个自增主键。
	通过mysql函数获取到刚插入记录的自增主键:
		LAST_INSERT_ID()

	是insert之后调用此函数

	修改insert
	 <!-- 添加用户
	 	parameterType:指定输入参数是pojo(包括用户信息)
	 	#{}中指定pojo的属性名,接收到pojo对象的属性值 ,mybatis通过OGNL获取对象的属性值
	  -->
	<insert id="insertUser" parameterType="my.mybatis.po.User">
		<!-- 
			将插入数据的主键返回,返回到user对象中
			
			SELECT LAST_INSERT_ID();得到刚insert进去记录的主键值,只适用于自增主键
			
			keyProperty:将查询到主键值设置到parameterType指定的对象的那个属性
			order:SELECT LAST_INSERT_ID():执行顺序,相对于insert语句的执行顺序
			resultType:指定select LAST_INSERT_ID()的结果类型
		 -->
		 <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>

3.8.4、非自增主键返回(使用uuid())
	使用mysql的uuid()函数生成主键,需要修改表中id字段类型为string,长度设置为36位
		<!--
		 	使用mysql的uuid()生成主键
		 	执行过程:
		 		首先通过uuid()得到主键,将主键设置到user对象的id属性中
		 		其次在insert执行时,从user对象中取出id属性值 
		  -->
		 <selectKey keyProperty="address" order="BEFORE" resultType="java.lang.String">
		 	select uuid()
		 </selectKey>
    
    通过oracle的序列生成主键
    	<selectKey keyProperty="address" order="BEFORE" resultType="java.lang.String">
		 	select 序列名.nextval()
		 </selectKey>

3.9、删除用户
3.9.1、映射文件
	<!-- 删除用户 
		根据id删除用户,需输入id值
	-->
	<delete id="deleteUser" parameterType="java.lang.Integer">
		delete from user where id = #{id}
	</delete>

3.9.2、代码
	// 删除用户
	@Test
	public void deleteUser() throws IOException, ParseException {
		// mybatis配置文件
		String resource = "SqlMapConfig.xml";
		InputStream stream = Resources.getResourceAsStream(resource);
		// 创建会话工厂,传入mybatis的配置文件信息
		SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(stream);

		// 通过工厂得到SqlSession
		SqlSession sqlSession = sessionFactory.openSession();
		
		//传入id删除用户
		sqlSession.delete("test.deleteUser", 1);
		// 提交事务
		sqlSession.commit();

		// 关闭会话
		sqlSession.close();
	}	

3.9、删除用户
3.9.1、映射文件
	<!-- 根据id更新用户
		分析:
			需要传入用户的id
			需传入用户的更新信息
			paremeterType指定user对象,包括id和更新信息,注意:id必须存在
				#{id}:从输入user对象中获取id属性值
	 -->
	 <update id="updateUser" parameterType="my.mybatis.po.User">
	 	update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
	 </update>

3.9.2、代码
	// 更新用户
	@Test
	public void updateUser() throws IOException, ParseException {
		// mybatis配置文件
		String resource = "SqlMapConfig.xml";
		InputStream stream = Resources.getResourceAsStream(resource);
		// 创建会话工厂,传入mybatis的配置文件信息
		SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(stream);
		
		// 通过工厂得到SqlSession
		SqlSession sqlSession = sessionFactory.openSession();
		User aUser = new User();
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
		Date parse = simpleDateFormat.parse("1921-10-21");
		aUser.setId(2);
		aUser.setSex("男");
//		aUser.setAddress("嗖嗖嗖");
		aUser.setBirthday(parse);
		aUser.setUsername("小1小采");
		//传入id删除用户
		sqlSession.delete("test.updateUser", aUser);
		// 提交事务
		sqlSession.commit();
		
		// 关闭会话
		sqlSession.close();
	}

3.11、总结
3.11.1、parameterType
	在映射文件中通过parameterType指定输入参数的类型

3.11.2、resultType
	在映射文件中通过resultType指定输出结果的类型

3.11.3、#{}和${}
	#{}表示一个占位符号,#{}接受输入参数,类型可以是简单类型:pojo、hashmap
	
	如果接受简单类型,#{}中可以写成value或其他名称

	#{}接受pojo对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性...的方式获取对象属性值

	${}表示一个拼接符号,会引用sql注入,所以不建议使用${}

	${}接受输入参数,类型可以是简单类型,pojo、hashmap

	如果接受简单类型,${}中只能写成value

	${}接受pojo对象值,通过OGNL读取对象中的属性值,通过性.属性.属性...的方式获取对象属性值

3.11.4、selectOne和selectList
	selectOne表示查询出一条记录进行映射,如果使用selectOne可以实现使用selectList也可以实现(list中只有一个对象)

	selectList表示查询出一个列表(多条记录)进行映射,如果使用selectList查询多条记录,不能使用selectOne 
		报TooManyResultsException:期望1个但是有多个出现

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

	将SqlSesionFactoryBuilder:当成一个工具类使用即可,不需要使用单例管理SqlSessionFactoryBuilder

	在需要创建SqlSessionFactory时候,只需要new一次SqlSessionFactoryBuilder即可。


4.1.2、SqlSessionFactory
	通过SqlSessionFactory创建SqlSession,使用单例模式管理SqlSessionFactory(工厂一旦创建,使用一个实例)

	将来mybatis和spring整合后,使用单例模式管理SqlSessionFactory

4.1.3、SqlSession
	SqlSession是一个面向用户(程序员)的接口

	SqlSession中提供了很多操作数据库的方法:比如:selectOne(返回单个对象)、selectList(返回单个或多个对象)

	SqlSession是线程不安全的,在SqlSession实现类中除了有接口中的方法(操作数据库的方法)还有数据域属性
	(struts2多例管理 数据域属性就是客户传入对象的username属性之类)

	SqlSession最佳应用场合在方法体内(线程不安全建议放这),定义成局部变量使用

4.2、原始dao开发方法(程序员需要写dao接口和dao实现类)
4.2.1、思路
	程序员需要写dao接口和dao实现类
	需要向dao实现类中注入SqlSessionFactory,在方法体内通过SqlSessionFactory创建SqlSession

4.2.2、dao接口
	public interface UserDao {
	//根据id查询用户信息
	public User findUserById(int id) throws Exception;
	
	//添加用户信息
	public void insertUser(User user) throws Exception;
	
	//根据用户名模糊查询用户列表
	public List<User> findUserByName(String name) throws Exception;
	
	//删除用户信息
	public void deleteUSer(int id) throws Exception;
	}

4.2.3、dao接口实现类
	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 List<User> findUserByName(String name) throws Exception {
			SqlSession sqlSession = sqlSessionFactory.openSession();
			
			List<User> list = sqlSession.selectList("test.findUserByName", name);
			
			//释放资源
			sqlSession.close();
			
			return list;
		}
		
		@Override
		public void insertUser(User user) throws Exception {
			SqlSession sqlSession = sqlSessionFactory.openSession();
			
			sqlSession.insert("test.insertUser",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();
		
		}

	}

4.2.4、测试代码
	public class UserDaoImplTest {
		//此方法执行在testFindUserById之前
		//单元测试
		private SqlSessionFactory sqlSessionFactory;
		@Before
		public void setUp() throws Exception {
			// 创建SqlSessionFactory
			// mybatis配置文件
			String resource = "SqlMapConfig.xml";
			InputStream stream = Resources.getResourceAsStream(resource);
			// 创建会话工厂,传入mybatis的配置文件信息
			//在实例前再加SqlSessionFactory会成为这个方法中的局部变量不能被其他方法引用也就是上面定义的private会为空
			sqlSessionFactory = new SqlSessionFactoryBuilder()
					.build(stream);
		}

		@Test
		public void testFindUserById() throws Exception {
			//创建UserDao的对象
			UserDao userDao = new UserDaoImpl(sqlSessionFactory);
			
			//调用UserDao的方法
			List<User> user = userDao.findUserByName("小");
			
			System.out.println(user);
		}

	}

4.2.5、总结原始dao开发问题
1、dao接口实现类方法中存在大量模版方法,设想能否将这些代码提取出来,大大减轻程序员的工作量

2、调用sqlSession方法时将statement的id硬编码了

3、调用sqlSession方法时传入的变量,由于sqlSession方法使用泛型,即使变量类型传入错误,在编译阶段也不报错,
不利于程序员开发


4.3、mapper代理方法(程序员只需要mapper接口(相当于dao接口))
4.3.1、思路
	程序员只需要mapper接口(相当于dao接口)

	程序员还需要编写mapper.xml映射文件

	程序员编写mapper接口需要遵循一些开发规范,mybatis可以自动生成mapper接口实现类代理对象

	开发规范:
		1、在mapper.xml中namespace等于mapper接口地址
			<!-- namespace命名空间,作用就是对sql进行分类化管理,理解为sql隔离 
				使用mapper代理方法开发,namespace有特殊重要的作用,namespace等于mapper接口地址
			-->

			<mapper namespace="my.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="my.mybatis.po.User">
			 	SELECT  * FROM USER WHERE id=#{value}
			 </select>

			//根据id查询用户信息
			public User findUserById(int id) throws Exception;

总结:
	以上开发规范主要是对下边代码进行统一生成
		User user = sqlSession.selectOne("test.findUserById", id);
		sqlSession.insert("test.insertUser",user);
		crud方法

4.3.2、mapper.java
	public interface UserMapper {
	
		//根据id查询用户信息
		public User findUserById(int id) throws Exception;
		
	//	//添加用户信息
	//	public void insertUser(User user) throws Exception;
	//	
	//	//根据用户名模糊查询用户列表
	//	public List<User> findUserByName(String name) throws Exception;
	//	
	//	//删除用户信息
	//	public void deleteUSer(int id) throws Exception;
	}

4.3.3、mapper.xml
	 <select id="findUserById" parameterType="int" resultType="my.mybatis.po.User">
	 	SELECT  * FROM USER WHERE id=#{value}
	 </select>

4.3.4、在SqlMapConfig.xml中加载mapper.xml
	<mapper resource="mapper/UserMapper.xml"/>

4.3.5、测试
	public class UserMapperTest {
		// 此方法执行在testFindUserById之前
		// 单元测试
		private SqlSessionFactory sqlSessionFactory;

		@Before
		public void setUp() throws Exception {
			// 创建SqlSessionFactory
			// mybatis配置文件
			String resource = "SqlMapConfig.xml";
			InputStream stream = Resources.getResourceAsStream(resource);
			// 创建会话工厂,传入mybatis的配置文件信息
			// 在实例前再加SqlSessionFactory会成为这个方法中的局部变量不能被其他方法引用也就是上面定义的private会为空
			sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream);
		}

		@Test
		public void testFindUserById() throws Exception {
			SqlSession sqlSession = sqlSessionFactory.openSession();
			
			//创建UserMapper对象,mybatis自动生成mapper代理对象
			UserMapper mapper = sqlSession.getMapper(UserMapper.class);
			
			//调用userMapper的方法
			User user = mapper.findUserById(2);
			System.out.println(user);
		}

	}

4.3.6、一些问题总结
4.3.6.1、代理对象内部调用selectOne或selectList
	如果mapper方法返回单个pojo对象(非集合对象),代理对象内部通过selectOne查询数据库

	如果mapper方法返回集合对象,代理对象内部通过selectList查询数据库

4.3.6.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(映射器)
    
	mybatis将按照下面的顺序来加载属性:
		在properties元素体内定义的属性首先被读取

		然后会读取properties元素中resource或url加载的属性,它会覆盖已读取的同名属性

		最后读取parameterType传递的属性,它会覆盖已读取的同名属性

	因此,通过parameterType传递的属性具有最高优先级,resource或url加载的属性次之,最低优先级的是properties元素体内定义的属性。

	建议:
		不要在properties元素体内添加任何属性值,只将属性值定义在properties文件中。

		在properties文件中定义属性名要有一定的特殊性,如xxxx.xxx.xxx

5.1、properties属性
需求:
	将数据库连接参数单独配置在db.properties中,只需要在SqlMapConfig.xml中加载db.properties的属性值,
	在SqlMapConfig.xml中就不需要对数据库连接参数硬编码

	将数据库连接参数只配置在db.properties中,原因:方便对参数进行统一管理,其他xml可以引用该db.properties。
	配在properties有利于以后集群的时候统一修改(利用脚本等)

	jdbc.driver=com.mysql.jdbc.Driver
	jdbc.url=jdbc:mysql://localhost:3306/mybatis
	jdbc.username=root
	jdbc.password=123

	测试不仅要正确(预期)也要出现错误(故意修改)的现象

	在SqlMapConfig.xml加载属性文件
			<!-- 加载配置文件 -->
			<properties resource="db.properties"></properties>
			<!-- 和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>

5.2、settings全局参数配置
	mybatis框架在运行时可以调整一些运行参数

	比如:开启二级缓存、开启延迟加载

	比如:mybatis全局配置参数,全局参数将会影响mybatis的运行行为

settings

5.3、typeAliases(别名)
5.3.1、需求
	在mapper.xml中,定义很多的statement,statement需要parameterType指定输入参数的类型,
	需要resultType指定输出结果的映射类型

	如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名,
	在mapper.xml中通过别名定义,方便开发。

5.3.2、mybatis默认支持别名

别名Handlers


5.3.3、自定义别名
5.3.3.1、单个别名定义
	<!-- 别名定义 -->
	<typeAliases>
		<!-- 针对单个别名定义
		type:类型的路径 
		alias:别名
		 -->
		<typeAlias type="my.mybatis.po.User" alias="user"/>
	</typeAliases>

	引用别名:
		<select id="findUserById" parameterType="int" resultType="user">
	 		SELECT  * FROM USER WHERE id=#{value}
	 	</select>

5.3.3.2、批量定义(常用)
	<!-- 批量别名定义 
	指定包名,mybatis自动扫描包中的po类,自动定义别名,别名就是类名(首字母大写或小写都可以)
	-->
	<package name="my.mybatis.po"/>

5.4、typeHandlers(类型处理器)
	mybatis通过typeHandlers完成jdbc类型和java类型的转换

	通常情况下,mybatis提供的类型处理器满足日常需求不需要自定义

	类型处理器			  Java类型 				JDBC类型
	BooleanTypeHandler	  Boolean,boolean		任何兼容的布尔值
	ByteTypeHandler		  Byte,byte	 			任何兼容的数字或字节类型
	ShortTypeHandler 	  Short,short 			任何兼容的数字或短整形
	IntegerTypeHandler 	  Integer,int 			任何兼容的数字和整形
	LongTypeHandler 	  Long,long 			任何兼容的数字和长整型
	FloatTypeHandler 	  Float,float 			任何兼容的数字或单精度浮点型
	DoubleTypeHandler 	  Double,double 		任何兼容的数字或双精度浮点型
	BigDecimalTypeHandler BigDecimal 			任何兼容的数字或十进制小数类型
	StringTypeHandler 	  String 			    CHAR和VARCHAR类型
	ClobReaderTypeHandler java.io.Reader
	ClobTypeHandler 	  java.lang.String 		CLOB,LONGVARCHAR
	NStringTypeHandler 	  java.lang.String NCLOB
	BlobInputStreamTypeHandler java.io.InputStream
	ByteArrayTypeHandler  byte[] 				数据库兼容的字节流类型
	BlobTypeHandler 	  byte[] 				BLOB,LONGVARBINARY
	DateTypeHandler 	  java.util.Date  		TIMESTAMP
	DateOnlyTypeHandler   java.util.Date Date
	TimeOnlyTypeHandler   java.util.Date TIME
	SqlTimestampTypeHandler Timestamp(java.sql) TIMESTAMP类型
	SqlDateTypeHandler 	  Date(java.sql)		ATE类型
	SqlTimeTypeHandler 	  Time(java.sql) 		TIME类型
	ObjectTypeHandler 	  任意 					其他或未指定类型
	ENumTypeHandler 	  Enumeration类型 		VARCHAR任何兼容的字符串类型,作为代码存储(而不是索引)

5.5、mappers(映射配置)
5.5.1、通过resource加载单个映射文件
	<!-- 通过resource方法一次加载一个配置文件 -->
	<mapper resource="mapper/UserMapper.xml"/>

5.5.2、通过mapper接口加载
	<!-- 通过mapper接口加载映射文件 
	遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称名保持一致,且在一个目录中
	上边规范的前提是:使用的是mapper代理方法
	-->
	<mapper class="my.mybatis.mapper.UserMapper" />

	按照上边的规范,将mapper.java和mapper.xml放在一个目录,且同名

在这里插入图片描述

5.5.3、批量加载mapper(推荐)
	<!-- 批量加载mapper
	指定mapper接口的包名,mybatis自动扫描包下变所有的mapper接口进行加载
	遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称名保持一致,且在一个目录中
	上边规范的前提是:使用的是mapper代理方法
	 -->
	 <package name="my.mybatis.mapper"/>

Mapper.xml映射文件
mapper.xml映射文件中定义了操作数据库的sql,每个sql是一个statement,映射文件是mybatis的核心

6、输入映射
	通过parameterType指定输入参数的类型,类型可以是简单类型、hashmap、pojo的包装类型

6.1、传递pojo的包装对象
6.1.1、需求
	完成用户信息的综合查询,需要传入查询条件(可能包括用户信息、其他信息,比如商品、订单的)

6.1.2、定义包装类型pojo
	针对上边需求,使用自定义的包装类型的pojo

	在包装类型的pojo中将复杂的查询条件包装进去
	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}:取出pojo包装对象中性别值 
	#{userCustom.username}:取出pojo包装对象中用户名称
	 -->
	 <select id="findUserById" parameterType="my.mybatis.po.UserQueryVo" resultType="my.mybatis.po.UserCustom">
	 	SELECT  * FROM USER WHERE user.sex = #{userCustom.sex} and user.username Like '%${userCustom.username}%'
	 </select>

6.1.4、mapper.java
	public interface UserMapper {
	
		//用户信息综合查询
		public List<UserCustom> findUserList(UserQueryVo userQueryVo) throws Exception;

6.1.5、测试代码
	public void testFindUserById() throws Exception {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		
		//创建UserMapper对象,mybatis自动生成mapper代理对象
		UserMapper mapper = sqlSession.getMapper(UserMapper.class);
		
		//创建包装对象,设置查询条件
		UserQueryVo userQueryVo = new UserQueryVo();
		UserCustom userCustom = new UserCustom();
		userCustom.setSex("男");
		userCustom.setUsername("小");
		//需要将信息注入到QueryVo中否则空指针
		userQueryVo.setUserCustom(userCustom);
		//调用userMapper的方法
		List<UserCustom> list = mapper.findUserList(userQueryVo);
		System.out.println(list);
	}


7、输出映射
7.1、resultType
	使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。

	如果查询出来的列名和pojo中的属性名完全不一致,没有创建pojo对象。

	只要查询出来的列名和pojo中的属性有一个一致,就会创建pojo对象

7.1.1、输出简单类型
7.1.1.1、需求
	用户信息的综合查询列表总数,通过查询总数和上边用户综合查询列表才可以实现分页

7.1.2、mapper.xml
	<!-- 用户信息综合查询总数
	parameterType:指定输入类型和findUserList一样
	resultType:输出结果类型 
	 -->
	<select id="findUserCount" parameterType="my.mybatis.po.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对象,mybatis自动生成mapper代理对象
		UserMapper mapper = sqlSession.getMapper(UserMapper.class);
		
		//创建包装对象,设置查询条件
		UserQueryVo userQueryVo = new UserQueryVo();
		UserCustom userCustom = new UserCustom();
		userCustom.setSex("男");
		userCustom.setUsername("小");
		//需要将信息注入到QueryVo中否则空指针
		userQueryVo.setUserCustom(userCustom);
		//调用userMapper的方法
		int list = mapper.findUserCount(userQueryVo);
		System.out.println(list);
	}

7.1.1.5、小结
	查询出来的结果集只有一行且一列,可以使用简单类型进行输出映射

7.1.2、输出pojo对象和pojo列表
	不管是输出的pojo单个对象还是一个列表(list中包括pojo),在mapper.xml中resultType指定的类型是一样的。
	在mapper.java指定的方法返回值类型不一样
		1、输出单个pojo对象,方法返回值是单个对象类型。
			public User findUserById(int id) throws Exception;

		2、输出pojo对象list,方法返回值是List<Pojo>
			public List<User> findUserByName(String name) throws Exception;

	生成的动态代理对象中是根据mapper方法的返回值类型确定是调用selectOne(返回单个对象调用)还是selectList(返回集合对象调用)

输出hashmap
	输出pojo对象可以改用hashmap输出类型,将输出的字段名称(id,username等)作为map的key,value作为字段值

7.2、resultMap
	resultType可以指定pojo将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功。

	mybatis中使用resultMap完成高级输出结果映射

7.2.1、resultMap使用方法
	如果sql查询字段名和pojo的属性名不一致,可以通过定义一个resultMap将字段名和属性名作一个对应关系,
	resultMap实质上还需要将查询结果映射到pojo对象中。

	1、定义resultMap

	2、使用resultMap作为statement的输出映射类型

	resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。

7.2.2、将下边的sql使用UserCustom完成映射
	SELECT id id_,username username_ FROM USER WHERE id=#{value}

	userCustom类中属性名和上边查询列名不一致

7.2.2.1、定义resultMap
	<!-- 定义resultMap
	将SELECT id id_,username username_ FROM USER 和User类中的属性作一个映射关系
	
	type:resultMap最终映射的java对象类型,可以使用别名
	id:对resultMap的唯一标识
	 -->
	<resultMap type="user" id="userResultMap">
		<!-- id表示查询结果集中唯一标识
		cloumn:查询出来的里面
		property:type指定的pojo类型中的属性名
		最终resultMap对column和property作一个映射关系(对应关系)
		 -->
		<id column="id_" property="id"/>
		<!-- 
		result:对普通名映射定义
		column:查询出来的列名
		property:type指定的pojo类型中的属性名
		最终resultMap对column和property作一个映射关系(对应关系)
		 -->
		 <result column="username_" property="username"/>
	</resultMap>

7.2.2.2、使用resultMap作为statement的输出映射类型
	<!-- 使用resultMap进行输出映射
	resultMap:指定定义的resultMap的id,如果这个resultMap在其他的mapper文件,前边需要加namespace
	因为使用resultMap输出映射所以别忘了修改resultType为resultMap
	否则会提示Could not resolve type alias 'userResultMap'.  Cause: java.lang.ClassNotFoundException: Cannot find class: userResultMap
	 -->
	<select id="findUserByResultMap" parameterType="int" resultMap="userResultMap">
	 	SELECT id id_,username username_ FROM USER WHERE id=#{value}
	 </select>

7.2.2.3、mapper.java
	//根据id查询用户信息,使用resultMap输出
	public User findUserByResultMap(int id) throws Exception;

7.2.2.4、测试
	@Test
	public void testFindUserByResultMap() throws Exception {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		
		//创建UserMapper对象,mybatis自动生成mapper代理对象
		UserMapper mapper = sqlSession.getMapper(UserMapper.class);
		
		//调用userMapper的方法
		User list = mapper.findUserByResultMap(2);
		System.out.println(list);
	}

7.3、小结
	使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。

	如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和属性名之间作一个映射关系。

8、动态sql
8.1、什么是动态sql
	mybatis核心对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接、组装

8.2、需求
	用户信息综合查询列表和用户信息查询列表总数这两个statement的定义使用动态sql

	对查询条件进行判断,如果输入参数不为空才进行查询条件拼接。

8.3、mapper.xml
	<!--用户信息综合查询
	#{userCustom.sex}:取出pojo包装对象中性别值 
	#{userCustom.username}:取出pojo包装对象中用户名称
	 -->
	 <select id="findUserList" parameterType="my.mybatis.po.UserQueryVo" resultType="my.mybatis.po.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>

8.4、测试代码
	@Test
	public void testFindUserList() throws Exception {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		
		//创建UserMapper对象,mybatis自动生成mapper代理对象
		UserMapper mapper = sqlSession.getMapper(UserMapper.class);
		
		//创建包装对象,设置查询条件
		UserQueryVo userQueryVo = new UserQueryVo();
		UserCustom userCustom = new UserCustom();
		//由于这里使用动态sql,如果不设置某个值,条件不会拼接在sql中
//		userCustom.setSex("男");
		userCustom.setUsername("小");
		//需要将信息注入到QueryVo中否则空指针
		userQueryVo.setUserCustom(userCustom);
		//调用userMapper的方法
		List<UserCustom> list = mapper.findUserList(userQueryVo);
		System.out.println(list);
	}

8.5、sql片段
8.5.1、需求
	将上边实现的动态sql判断代码块抽取出来,组成一个sql片段。其他的statement中就可以引用sql片段

8.5.2、定义sql片段
	<!-- 定义sql片段
	id:sql片段的唯一标识
	
	经验:是基于单表来定义sql片段,这样的话这个sql片段可重用性才高
	在sql片段中不要包括where 因为可能有多个and,判断语句较多
	 -->
	<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}:取出pojo包装对象中性别值 
	#{userCustom.username}:取出pojo包装对象中用户名称
	 -->
	 <select id="findUserList" parameterType="my.mybatis.po.UserQueryVo" resultType="my.mybatis.po.UserCustom">
	 	SELECT  * FROM USER 
	 	<!-- 
	 		where可以自动去掉条件中的第一个and
	 	 -->
	 	<where>
	 		<!-- 引用sql片段的id,如果refid指定的id不再本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
	public class UserQueryVo {
	
	//传入多个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:开始遍历时拼接串
 		 -->
 		 <!-- 实现下边的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>

 8.6.4、测试代码
	@Test
	public void testFindUserList() throws Exception {
		SqlSession sqlSession = sqlSessionFactory.openSession();
		
		//创建UserMapper对象,mybatis自动生成mapper代理对象
		UserMapper mapper = sqlSession.getMapper(UserMapper.class);
		
		//创建包装对象,设置查询条件
		UserQueryVo userQueryVo = new UserQueryVo();
		UserCustom userCustom = new UserCustom();
		//由于这里使用动态sql,如果不设置某个值,条件不会拼接在sql中
//		userCustom.setSex("男");
		userCustom.setUsername("小");
		ArrayList<Integer> ids = new ArrayList<Integer>();
		ids.add(1);
		ids.add(2);
		//需要将信息注入到QueryVo中否则空指针
		userQueryVo.setIds(ids);
		userQueryVo.setUserCustom(userCustom);
		//调用userMapper的方法
		List<UserCustom> list = mapper.findUserList(userQueryVo);
		System.out.println(list);
	}

另一个sql的实现
	 <!-- 实现下边的sql拼接
	 AND id in(1,10,16)
	  -->
	<foreach collection="ids" item="user_id" open=" AND id in (" close=")" separator=",">
		<!-- 每个遍历需要拼接的串 -->
		#{user_id}
	</foreach>

猜你喜欢

转载自blog.csdn.net/civilizationV/article/details/82850990