SSM开发框架

  • Spring:对象容器框架,可以对系统中的对象进行系统的管理,是框架的框架
  • Spring MVC:Spring的一个分支产品,可以有效地管理Web层面的交互
  • MyBatis:简化了数据库增删改查的操作

image.png

MyBatis介绍

  • Mybatis是优秀的持久层框架
  • MyBatis使用XML将SQL与程序解耦,便于维护
  • MyBatis学习简单,执行高效,是JDBC的延伸

MyBatis官网:https://mybatis.org/mybatis-3/zh/index.html

MyBatis开发流程

  1. 引入MyBatis依赖
  2. 创建核心配置文件
  3. 创建实体(Entity)类
  4. 创建Mapper映射文件
  5. 初始化SessionFactory
  6. 利用SqlSession对象操作数据

MyBatis环境配置

配置文件:mybatis-config.xml

  • MyBatis采用XML格式皮遏制数据库环境信息
  • MyBatis环境配置标签
  • environment包含数据库驱动,URL,用户名和密码

image.png

第一步:打开IDEA,选择Maven创建一个新的项目

第二步:修改pom.xml文件
pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>com.song</groupId>
  7. <artifactId>mybatis</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <repositories>
  10. <repository>
  11. <id>aliyun</id>
  12. <name>aliyun</name>
  13. <url>https://maven.aliyun.com/repository/public</url>
  14. </repository>
  15. </repositories>
  16. <dependencies>
  17. <dependency>
  18. <groupId>org.mybatis</groupId>
  19. <artifactId>mybatis</artifactId>
  20. <version>3.5.3</version>
  21. </dependency>
  22. <dependency>
  23. <groupId>mysql</groupId>
  24. <artifactId>mysql-connector-java</artifactId>
  25. <version>5.1.47</version>
  26. </dependency>
  27. </dependencies>
  28. </project>

第三步:新建mybatis-config.xml文件
src/main/resources/mybatis-config.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE configuration
  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6. <!--设置默认指向的数据库-->
  7. <environments default="dev">
  8. <!--配置环境,不同的环境不同的id名字-->
  9. <environment id="dev">
  10. <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
  11. <transactionManager type="JDBC"></transactionManager>
  12. <!--采用连接池方式管理数据库连接-->
  13. <dataSource type="POOLED">
  14. <property name="driver" value="com.mysql.jdbc.Driver"/>
  15. <property name="url"
  16. value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
  17. <property name="username" value="root"/>
  18. <property name="password" value="xxhacys2015"/>
  19. </dataSource>
  20. </environment>
  21. <environment id="prd">
  22. <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
  23. <transactionManager type="JDBC"></transactionManager>
  24. <!--采用连接池方式管理数据库连接-->
  25. <dataSource type="POOLED">
  26. <property name="driver" value="com.mysql.jdbc.Driver"/>
  27. <property name="url"
  28. value="jdbc:mysql://192.168.1.155:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
  29. <property name="username" value="root"/>
  30. <property name="password" value="root"/>
  31. </dataSource>
  32. </environment>
  33. </environments>
  34. </configuration>

SqlSessionFactory

  • SqlSessionFactory是MyBatis的核心对象

  • 用于初始化MyBatis,创建SqlSession对象

  • 保证SqlSessionFactory在应用中全局唯一

SqlSession

  • SqlSession是MyBatis操作数据库的核心对象
  • SqlSession使用JDBC方式与数据库交互
  • SqlSession对象提供了数据表CRUD对应方法

初始化工具类MyBatisUtils

src/main/java/com.song.mybatis.utils/MyBatisUtils.java
#一般工具类都放在utils下面

  1. package com.song.mybatis.utils;
  2. import org.apache.ibatis.io.Resources;
  3. import org.apache.ibatis.session.SqlSession;
  4. import org.apache.ibatis.session.SqlSessionFactory;
  5. import org.apache.ibatis.session.SqlSessionFactoryBuilder;
  6. import java.io.IOException;
  7. import java.io.Reader;
  8. /**
  9. * MyBatisUtils工具类,创建全局唯一的SqlSessionFactory对象
  10. */
  11. public class MyBatisUtils {
  12. // 利用static(静态)属于类不属于对象,且全局唯一
  13. private static SqlSessionFactory sqlSessionFactory = null;
  14. // 利用静态块在初始化类时实例化sqlSessionFactory
  15. static{
  16. Reader reader = null;
  17. try {
  18. reader = Resources.getResourceAsReader("mybatis-config.xml");
  19. sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. // 初始化错误时,通过抛出异常ExceptionInInitializerError通知调用者
  23. throw new ExceptionInInitializerError(e);
  24. }
  25. }
  26. /**
  27. * openSession 创建一个新的SqlSession对象
  28. * @return SqlSession对象
  29. */
  30. public static SqlSession openSession(){
  31. return sqlSessionFactory.openSession();
  32. }
  33. /**
  34. * 释放一个有效的SqlSession对象
  35. * @param session 准备释放SqlSession对象
  36. */
  37. public static void closeSession(SqlSession session){
  38. if(session != null){
  39. session.close();
  40. }
  41. }
  42. }

MyBatis数据查询

  1. 创建实体类(Entity)
  2. 创建Mapper XML
  3. 编写SQL标签

src/main/resources/mappers/goods.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="goods">
  6. <select id="selectAll" resultType="com.song.mybatis.entity.Goods">
  7. select * from t_goods order by goods_id desc limit 10
  8. </select>
  9. </mapper>

到这里,就可以试试数据查询功能了
src/test/java/com.song.mybatis/MyBatisTestor.java

  1. package com.song.mybatis;
  2. import com.song.mybatis.entity.Goods;
  3. import com.song.mybatis.utils.MyBatisUtils;
  4. import org.apache.ibatis.session.SqlSession;
  5. import org.junit.Test;
  6. import java.util.List;
  7. public class MyBatisTestor {
  8. @Test
  9. public void testMyBatis(){
  10. SqlSession sqlSession = null;
  11. try{
  12. sqlSession = MyBatisUtils.openSession();
  13. List<Goods> list = sqlSession.selectList("goods.selectAll");
  14. for(Goods g:list){
  15. System.out.println(g.getTitle());
  16. }
  17. }catch (Exception e){
  18. e.printStackTrace();
  19. }finally {
  20. MyBatisUtils.closeSession(sqlSession);
  21. }
  22. }
  23. }

根据输出结果可以看到,数据库中的数据已经查询到了。但是存在一个问题,有些字段的内容没有查询到,
比如goodsId,subTitle等等。这是因为java程序中用的是goodsId,而数据库底层用的却是goods_id,它们并不一致,就导致查询不到了。那怎么解决这个问题呢?请继续往下看。

  1. 开启驼峰命名映射

src/main/resources/mybatis-config.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE configuration
  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6. <settings>
  7. <!-- goods_id ==> goodsId 驼峰命名转换 -->
  8. <setting name="mapUnderscoreToCamelCase" value="true"/>
  9. </settings>
  10. <!--设置默认指向的数据库-->
  11. <environments default="dev">
  12. <!--配置环境,不同的环境不同的id名字-->
  13. <environment id="dev">
  14. <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
  15. <transactionManager type="JDBC"></transactionManager>
  16. <!--采用连接池方式管理数据库连接-->
  17. <dataSource type="POOLED">
  18. <property name="driver" value="com.mysql.jdbc.Driver"/>
  19. <property name="url"
  20. value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
  21. <property name="username" value="root"/>
  22. <property name="password" value="xxhacys2015"/>
  23. </dataSource>
  24. </environment>
  25. <environment id="prd">
  26. <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
  27. <transactionManager type="JDBC"></transactionManager>
  28. <!--采用连接池方式管理数据库连接-->
  29. <dataSource type="POOLED">
  30. <property name="driverClass" value="com.mysql.jdbc.Driver"/>
  31. <property name="url"
  32. value="jdbc:mysql://192.168.1.155:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
  33. <property name="username" value="root"/>
  34. <property name="password" value="root"/>
  35. </dataSource>
  36. </environment>
  37. </environments>
  38. <mappers>
  39. <mapper resource="mappers/goods.xml"/>
  40. </mappers>
  41. </configuration>

这样一来,所有的数据都能查询到了

SQL传参

src/main/resources/mappers/goods.xml
#单参数传递和多参数传递

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="goods">
  6. <select id="selectAll" resultType="com.song.mybatis.entity.Goods">
  7. select * from t_goods order by goods_id desc limit 10
  8. </select>
  9. <!-- 单参数传递,使用parameterType指定参数的数据类型即可,SQL中#{value}提取参数 -->
  10. <select id="selectById" parameterType="Integer" resultType="com.song.mybatis.entity.Goods">
  11. select * from t_goods where goods_id = #{value}
  12. </select>
  13. <!-- 多参数传递,使用parameterType指定Map接口,SQL中#{key}提取参数 -->
  14. <select id="selectByPriceRange" parameterType="java.util.Map" resultType="com.song.mybatis.entity.Goods">
  15. select * from t_goods
  16. where
  17. current_price between #{min} and #{max}
  18. order by current_price
  19. limit 0,#{limit}
  20. </select>
  21. </mapper>

测试:
src/test/java/com.song.mybatis/MyBatisTestor.java
#根据结果可以看到,我们想要的数据已经查询到了

  1. package com.song.mybatis;
  2. import com.song.mybatis.entity.Goods;
  3. import com.song.mybatis.utils.MyBatisUtils;
  4. import org.apache.ibatis.session.SqlSession;
  5. import org.junit.Test;
  6. import java.util.HashMap;
  7. import java.util.List;
  8. import java.util.Map;
  9. public class MyBatisTestor {
  10. @Test
  11. public void testSelectById(){
  12. SqlSession sqlSession = null;
  13. try{
  14. sqlSession = MyBatisUtils.openSession();
  15. Goods goods = sqlSession.selectOne("goods.selectById", 1602);
  16. System.out.println(goods.getTitle());
  17. }catch (Exception e){
  18. e.printStackTrace();
  19. }finally {
  20. MyBatisUtils.closeSession(sqlSession);
  21. }
  22. }
  23. @Test
  24. public void testSelectByPriceRange(){
  25. SqlSession sqlSession = null;
  26. try{
  27. sqlSession = MyBatisUtils.openSession();
  28. Map param = new HashMap<>();
  29. param.put("min",100);
  30. param.put("max",500);
  31. param.put("limit",10);
  32. List<Goods> list = sqlSession.selectList("goods.selectByPriceRange", param);
  33. for(Goods g:list){
  34. System.out.println(g.getTitle());
  35. }
  36. }catch (Exception e){
  37. e.printStackTrace();
  38. }finally {
  39. MyBatisUtils.closeSession(sqlSession);
  40. }
  41. }
  42. }

多表关联查询

src/main/resources/mappers/goods.xml

  1. <!-- 利用LinkedHashMap保存多表关联结果
  2. MyBatis会将每一条记录包装为LinkedHashMap对象
  3. key是字段名,value是字段对应的值,字段类型根据表结构进行自动判断
  4. 优点:易于扩展,易于使用
  5. 缺点:太过灵活,无法进行编译时检查
  6. -->
  7. <select id="selectGoodsMap" resultType="java.util.LinkedHashMap">
  8. select g.*, c.category_name from t_goods g, t_category c
  9. where g.category_id = c.category_id
  10. </select>

使用LinkedHashMap的原因:可以保持顺序
#如果直接使用Map的话,查询出来的结果是无序的

src/test/java/com.song.mybatis/MyBatisTestor.java

  1. @Test
  2. public void testSelectGoodsMap(){
  3. SqlSession session = null;
  4. try{
  5. session = MyBatisUtils.openSession();
  6. List<Map> list = session.selectList("goods.selectGoodsMap");
  7. for(Map map:list){
  8. System.out.println(map);
  9. }
  10. }catch (Exception e){
  11. e.printStackTrace();
  12. }finally {
  13. MyBatisUtils.closeSession(session);
  14. }
  15. }

ResultMap结果映射

用LinkedHashMap保存查询结果的体验并不是很好,那么我们可以使用ResultMap

  • ResultMap可以将查询结果映射为复杂类型的Java对象
  • ResultMap适用于Java对象保存多表关联结果
  • ResultMap支持对象关联查询等高级特性

Data Transfer Object — 数据传输对象

src/main/resources/mappers/goods.xml

  1. <!-- 结果映射 -->
  2. <resultMap id="rmGoods" type="com.song.mybatis.dto.GoodsDTO">
  3. <!-- 设置主键字段与属性映射 -->
  4. <id property="goods.goodsId" column="goods_id"></id>
  5. <!-- 设置非主键字段与属性映射 -->
  6. <result property="goods.title" column="title"></result>
  7. <result property="goods.originalCost" column="original_cost"></result>
  8. <result property="goods.currentPrice" column="current_price"></result>
  9. <result property="goods.discount" column="discount"></result>
  10. <result property="goods.isFreeDelivery" column="is_free_delivery"></result>
  11. <result property="goods.categoryId" column="category_id"></result>
  12. <result property="category.categoryId" column="category_id"></result>
  13. <result property="category.categoryName" column="category_name"></result>
  14. <result property="category.parentId" column="prent_id"></result>
  15. <result property="category.categoryLevel" column="category_level"></result>
  16. <result property="category.categoryOrder" column="category_order "></result>
  17. </resultMap>
  18. <select id="selectGoodsDTO" resultMap="rmGoods">
  19. select g.*, c.* from t_goods g, t_category c
  20. where g.category_id = c.category_id
  21. </select>

com.song.mybatis.dto.GoodsDTO
#自定义数据传输对象

  1. package com.song.mybatis.dto;
  2. import com.song.mybatis.entity.Category;
  3. import com.song.mybatis.entity.Goods;
  4. // Data Transfer Object -- 数据传输对象
  5. public class GoodsDTO {
  6. private Goods goods = new Goods();
  7. private Category category = new Category();
  8. public Goods getGoods() {
  9. return goods;
  10. }
  11. public void setGoods(Goods goods) {
  12. this.goods = goods;
  13. }
  14. public Category getCategory() {
  15. return category;
  16. }
  17. public void setCategory(Category category) {
  18. this.category = category;
  19. }
  20. }

src/test/java/com.song.mybatis/MyBatisTestor.java

  1. @Test
  2. public void testSelectGoodsDTO(){
  3. SqlSession session = null;
  4. try{
  5. session = MyBatisUtils.openSession();
  6. List<GoodsDTO> list = session.selectList("goods.selectGoodsDTO");
  7. for(GoodsDTO g:list){
  8. System.out.println(g.getGoods().getTitle());
  9. }
  10. }catch (Exception e){
  11. e.printStackTrace();
  12. }finally {
  13. MyBatisUtils.closeSession(session);
  14. }
  15. }

数据插入操作

image.png

第一步,需要修改MyBaticUtils
src/main/java/com.song.mybatis/utils/MyBatisUtils.java

  1. /**
  2. * openSession 创建一个新的SqlSession对象
  3. * @return SqlSession对象
  4. */
  5. public static SqlSession openSession(){
  6. // 默认SqlSession会自动提交事务数据(commit)
  7. // 设置false代表关闭自动提交,改为手动提交事务数据
  8. return sqlSessionFactory.openSession(false);
  9. }

第二步,新增sql处理
src/main/resources/mappers/goods.xml
#selectkey的作用是在写入数据之后,更新主键goodsId

  1. <insert id="insert" parameterType="com.song.mybatis.entity.Goods">
  2. INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
  3. VALUES (#{title}, #{subTitle}, #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
  4. <selectKey resultType="Integer" keyProperty="goodsId" order="AFTER">
  5. select last_insert_id()
  6. </selectKey>
  7. </insert>

第三步,测试插入处理
src/test/java/com.song.mybatis/MyBatisTestor.java

  1. @Test
  2. public void testInsert(){
  3. SqlSession session = null;
  4. try{
  5. session = MyBatisUtils.openSession();
  6. Goods goods = new Goods();
  7. goods.setTitle("测试商品");
  8. goods.setSubTitle("测试子标题");
  9. goods.setOriginalCost(200f);
  10. goods.setCurrentPrice(100f);
  11. goods.setDiscount(0.5f);
  12. goods.setIsFreeDelivery(1);
  13. goods.setCategoryId(43);
  14. // insert()方法返回值代表本次成功插入的记录总数
  15. int num = session.insert("goods.insert", goods);
  16. session.commit(); // 提交事务数据
  17. }catch (Exception e){
  18. if(session != null){
  19. session.rollback(); // 回滚事务
  20. }
  21. e.printStackTrace();
  22. }finally {
  23. MyBatisUtils.closeSession(session);
  24. }
  25. }

selectKey与useGeneratedKeys的区别image.png

image.png

  • selectKey标签需要明确编写最新主键的SQL语句
  • useGeneratedKeys属性会自动根据驱动生成对应SQL语句
  • selectKey适用于所有的关系型数据库
  • useGeneratedKeys只支持“自增主键”类型的数据库

总结:

  • selectKey标签是通用方案,适用于所有数据库,但编写麻烦

迁移到别的数据库,可能还需要修改selectKey里面的内容

  • useGeneratedKeys属性只支持“自增主键”数据库,使用简单

心得:

  • 优先推荐使用useGeneratedKeys
  • 但是考虑的复杂的业务场景,如果有很多种数据库要对数据进行支撑和保存的时候,就用selectKey

扩展:
在Oracle中selectKey的用法
image.png

数据更新操作

image.png

src/main/resources/mappers/goods.xml

  1. <update id="update" parameterType="com.song.mybatis.entity.Goods">
  2. UPDATE t_goods
  3. SET
  4. title = #{title},
  5. sub_title = #{subTitle},
  6. original_cost = #{originalCost},
  7. current_price = #{currentPrice},
  8. discount = #{discount},
  9. is_free_delivery = #{isFreeDelivery},
  10. category_id = #{categoryId}
  11. WHERE
  12. goods_id = #{goodsId}
  13. </update

src/test/java/com.song.mybatis/MyBatisTestor.java

  1. @Test
  2. public void testUpdate(){
  3. SqlSession session = null;
  4. try{
  5. session = MyBatisUtils.openSession();
  6. Goods goods = session.selectOne("goods.selectById",739);
  7. goods.setTitle("更新测试商品");
  8. int num = session.update("goods.update",goods);
  9. session.commit(); // 提交事务数据
  10. }catch (Exception e){
  11. if(session != null){
  12. session.rollback(); // 回滚事务
  13. }
  14. e.printStackTrace();
  15. }finally {
  16. MyBatisUtils.closeSession(session);
  17. }
  18. }

数据删除操作

image.png

src/main/resources/mappers/goods.xml

  1. <delete id="delete" parameterType="Integer">
  2. delete from t_goods where goods_id = #{value}
  3. </delete>

src/test/java/com.song.mybatis/MyBatisTestor.java

  1. @Test
  2. public void testDelete(){
  3. SqlSession session = null;
  4. try{
  5. session = MyBatisUtils.openSession();
  6. int num = session.delete("goods.delete",739);
  7. session.commit(); // 提交事务数据
  8. }catch (Exception e){
  9. if(session != null){
  10. session.rollback(); // 回滚事务
  11. }
  12. e.printStackTrace();
  13. }finally {
  14. MyBatisUtils.closeSession(session);
  15. }
  16. }

预防SQL注入攻击

SQL注入是指攻击者利用SQL漏洞,绕过系统约束,越权获取数据的攻击方式
image.png

MyBatis两种传值方式

  • ${}文本替换,未经任何处理对SQL文本替换
  • {}预编译传值,使用预编译传值可以预防SQL注入

MyBatis工作流程

image.png