MyBatis笔记自用(1)

MyBatis学习前需了解的知识

在学习mybatis之前,先学习一下代理模式

1 代理模式

1.1 什么是代理模式?

  • 代理模式为一个对象提供一个代理对象,以控制对这个对象的访问。即通过代理对象访问目标对象,这样做的好处是:可以在不修改目标对象代码的基础上,增强额外的功能操作,即扩展目标对象的功能
  • 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
  • 代理模式有不同的形式,主要有静态代理、动态代理和 Cglib代理三种形式

1.2 核心角色

  • 抽象角色(接口):定义公共对外方法。

  • 真实角色(周杰伦):实现抽象角色,定义真实角色所要实现的业务逻辑

  • 代理角色(代理人):实现抽象角色,是真实角色的代理,通过调用真实角色的方法来完成业务逻辑,并可以附加自己的操作

image-20240605215035255

1.3 静态代理模式

话不多说,直接上案例

  1. 新建项目proxy,目录结构如下

    image-20240605215520892

  2. 新建Star抽象角色,里面放了所有的功能

    public interface Star {
        /**
         * 面谈
         */
        void confer();
        /**
         * 签合同
         */
        void signContract();
        /**
         * 订票
         */
        void bookTicket();
        /**
         * 唱歌
         */
        void sing();
        /**
         * 收钱
         */
        void collectMoney();
    }
    
  3. RealStar,真实角色,继承抽象角色,周杰伦所干的事

    public class RealStar implements Star{
        @Override
        public void confer() {
    
        }
    
        @Override
        public void signContract() {
    
        }
    
        @Override
        public void bookTicket() {
    
        }
    
        @Override
        public void sing() {
            System.out.println("周杰伦:快使用双截棍");
        }
    
        @Override
        public void collectMoney() {
    
        }
    }
    
  4. ProxyFactory,代理角色,经济人所干的事

    package com.by.proxy.StaticProxy;
    
    /**
     * @author qc
     * @version 0.1
     * @className ProxyStar
     * @date 2024/6/4 18:55
     * @since jdk11
     */
    public class ProxyStar implements Star{
        private Star star;
    
        public ProxyStar(Star star) {
            this.star = star;
        }
    
        @Override
        public void confer() {
            System.out.println("宋吉吉正在:confer");
        }
    
        @Override
        public void signContract() {
            System.out.println("宋吉吉正在:signContract");
        }
    
        @Override
        public void bookTicket() {
            System.out.println("宋吉吉正在:bookTicket");
        }
    
        @Override
        public void sing() {
            //让周杰伦唱歌
            this.star.sing();
        }
    
        @Override
        public void collectMoney() {
            System.out.println("宋吉吉正在:collectMoney");
        }
    }
    
  5. 测试,新建客户端Client,也就是用户

    public class Client {
        public static void main(String[] args) {
            Star proxy = new ProxyStar(new RealStar());
    
            proxy.confer();
            proxy.signContract();
            proxy.bookTicket();
            proxy.sing();
    
            proxy.collectMoney();
        }
    }
    

    结果如下
    image-20240606123106596

    各司其职,每个角色都做了自己该做的事1

1.3.1 静态代理的缺点

  1. 代理类和真实角色类也就是周杰伦,都实现了抽象角色的方法,真实角色只需要唱歌即可,有大量代码重复。
  2. 代理对象只服务于一种类型的对象,假设一个系统中有100个Service,则需要创建100个代理对象。

1.4 动态代理模式

为了解决静态代理的缺点,就引入了动态代理模式,动态代理模式就是新建一个代理工厂,工厂里有很多代理类,调用获取代理类的方法来获取代理类,这样就解决的静态代理的缺点

案例

  1. 新建Star接口,只写一个sing方法

    public interface Star {
        void sing();
    }
    
  2. 新建realStar,实现该接口,此时这个实现类只用重写一个sing方法

    public class RealStar implements Star {
    
        @Override
        public void sing() {
            System.out.println("周杰伦:快使用双截棍");
        }
    }
    
  3. 新建一个代理工厂,新建getProxyObject方法,该方法目的就是获取代理对象

    public class ProxyFactory {
        private Object realStar;
    
        public ProxyFactory(Object realStar) {
            this.realStar = realStar;
        }
    
        //获取代理对象
        public Object getProxyObject(){
            /*
              Proxy:作用创建代理对象
                   ClassLoader loader:类加载器
                   Class<?>[] interfaces:真实角色实现的接口,根据接口生成代理类
                   InvocationHandler h:增强的逻辑,即如何代理(宋吉吉要做的事)
             */
            return Proxy.newProxyInstance(realStar.getClass().getClassLoader(),
                    realStar.getClass().getInterfaces(),
                    new InvocationHandler() {
                        /**
                         * @param proxy:代理类,一般不用
                         * @param method:要调用的方法
                         * @param args:调用方法时的参数
                         */
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println("宋吉吉正在:confer");
                            System.out.println("宋吉吉正在:signContract");
                            System.out.println("宋吉吉正在:bookTicket");
                            Object invoke = method.invoke(realStar, args);
                            System.out.println("宋吉吉正在:collectMoney");
                            return invoke;
                        }
                    });
        }
    }
    
  4. 新建用户类,在里面调用方法

    public class Client {
        public static void main(String[] args) {
            //new一个ProxyFactory工厂将RealStar传进去再调用getProxyObject方法获取到一个代理对象
            Star proxyObject = (Star) new ProxyFactory(new RealStar()).getProxyObject();
            //通过该代理对象可以直接调用sing方法,实际调用的是invoke方法
            proxyObject.sing();
        }
    }
    

2 MyBatis介绍

2.1 什么是框架

在介绍什么是mybatis之前首先了解一下框架,框架就是(Framework)是一个框子——指其约束性,也是一个架子——指其支撑性,即已经对基础的代码进行了封装并提供相应的API,开发者在使用框架是直接调用封装好的api可以省去很多代码编写,从而提高工作效率和开发速度。

2.2 传统jdbc的缺点

  1. 手动创建和释放链接

  2. sql语句在代码中硬编码

  3. 对结果的解析

2.3 什么是mybatis

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis。iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。

MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、获取结果集等jdbc繁杂的过程代码。具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性。

3 MyBatis入门案例

先创一个mybatis的数据库,然后在添加一个表,表里数据随便填填

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) NOT NULL COMMENT '用户名称',
  `password` varchar(20) DEFAULT NULL,
  `birthday` datetime DEFAULT NULL COMMENT '生日',
  `sex` char(1) DEFAULT NULL COMMENT '性别',
  `address` varchar(256) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=49 DEFAULT CHARSET=utf8;
image-20240606211221962

项目目录如下:

image-20240611144724357

3.1 添加必要的依赖

pom.xml

 <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <!--这里选择自己的mysql版本-->
            <version>5.1.49</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

     	<!--这个依赖可以选择性添加-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <!-- 如果不添加此节点src/main/java目录下的所有配置文件都会被漏掉。 -->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>

3.2 添加日志配置文件

log4j.properties,主要是对日志的一些打印,能够在控制台看到

# Global logging configuration
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.3 映射类

pojo/User.java

@Data
@Accessors(chain = true)
public class User {

  private Integer id;
  private String username;
  private Date birthday;
  private String sex;
  private String address;
}

3.4 UserMapper

提供查询方法

public interface UserMapper {
    /**
     * 查询所有用户信息
     * @return 存放用户信息
     */
    public List<User> findAll();
}

3.5 UserMapper.xml

<?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,一般是接口名称的全类名-->
<mapper namespace="com.by.mapper.UserMapper">
    <!--
      id:和接口方法名保持一致
      resultType:和接口返回类型保持一致
     -->
    <select id="findAll" resultType="com.by.pojo.User">
        select * from user
    </select>
</mapper>

3.6 mybatis-config.xml

存放所有配置信息,数据库连接信息,以及sql语句

<?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="dev">
        <!-- dev环境 -->
        <environment id="dev">
            <!-- 配置事务的类型:type="JDBC | MANAGED" 两种方式
                 JDBC:表示使用JDBC中原生的事务管理方式
                 MANAGED:被管理,例如Spring
			-->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置连接数据库的信息:用的是数据源(连接池) -->
            <dataSource type="POOLED">
                <!-- mysql5 -->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3307/mybatis?characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 告知 mybatis 映射配置的位置 -->
    <mappers>
        <mapper resource="com/by/mapper/UserMapper.xml"/>
    </mappers>
</configuration>
  • 注意mysql8的driver和url
<!-- mysql8 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&serverTimezone=Asia/Shanghai"/>

3.7 测试

test/java/com/by/test/MyBatisTest.java,单元测试

public class MyBatisTest {
    @Test
    public void test() throws Exception{
        // 加载配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        
        // 创建SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        
        //获得数据库会话实例
        SqlSession sqlSession = sqlSessionFactory.openSession();
        
        //得到代理类对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        
        List<User> userList = mapper.findAll();
        userList.forEach(System.out::println);
    }

}

运行结果

image-20240606212925381

4 MyBatis的运行原理

这里我总结了一张图,详细介绍了MyBatis的整体运行流程

image-20240606213251211

MyBatis框架在操作数据库时,大体经过了以下几个步骤:

  1. 读取配置信息:通过Resources的getResourceAsStream(“mybatis-config.xml”)方法来加载配置文件,并返回了一个InputStream流对象,里面存放了sql语句和连接信息(包含url,username,password,dirver等)
  2. 构造会话工厂SqlSessionFactory:new一个SqlSessionFactoryBuilder类,该类有一个build方法,将获取的is流传过去,会返回一个SqlSessionFactory。build方法内部先通过工具类XMLConfigBuilder调用loadConfiguration(is)方法将流传进来,获得一个Configuration对象,所以Configuration对象里就存放了is流里的一些配置信息,然后再将Configuration对象传入工厂对象DefaultSqlSessionFactory中并返回,因为DefaultSqlSessionFactory是SqlSessionFactory的实现类,所以返回的是SqlSessionFactory对象。
  3. Configuration如何储存配置信息:Configuration(conn, Map<namespace+“.”+id, MappedStatement(sql, resultType)>),包含了conn连接和MappedStatement,MappedStatement又放了一些sql语句和结果类型也就是一些映射文件。
  4. 创建会话对象SqlSession:SqlSession是由SqlSessionFactory会话工厂创建的,因为会话工厂实际返回的是DefaultSqlSessionFactory实现类,而DefaultSqlSessionFactory中实现了会话工厂的openSession()方法,该方法返回了一个SqlSession会话对象。
  5. 获取代理对象:SqlSession对象内部有很多和数据库相关的方法,这里调用getMapper()这个方法,这个方法的参数传的是接口的.class对象,内部是返回了一个代理对象 return Proxy.newProxyInstance(…) 内部三个参数,第一个是类加载器,第二个是接口,第三个new ProxyFactory是一个代理工厂,通过代理工厂得到一个代理对象。
  6. ProxyFactory:proxyFactory调用代理对象的任何方法,都会在invoke中执行,根据method的getName和getDeclaringClass().getName()获得方法名和方法所在类的名字组合到一起得到Map的key,通过key获取value值,也就是获取到了一个MappedStatement,再将连接conn和MappedStatenment传入执行器Executor中。
  7. Executor:执行器,主要是发送sql语句,输出结果映射.

5 自定义Mybatis

了解MyBatis的核心对象即可

5.1 MyBatis的核心对象

  • Resources

    加载配置文件,有一种是使用类加载进行加载,我们通过这个类的类加载器进行资源的加载。

  • SqlSessionFactoryBuilder

    构建SqlSessionFactory工厂对象需要的对象。采用了构建者模式,屏蔽了对象构建的细节。

  • SqlSessionFactory

    创建SqlSession对象所用。使用工厂模式创建,目的就是解耦合。

  • SqlSession

    创建代理对象,使用了代理模式

  • Executor

    操作数据库

  • MappedStatement

    存储SQL语句、参数、输出结果类型

6 MyBatis的CRUD

新建项目,完善项目

image-20240611102439146

6.1 查询

6.1.1 单个参数绑定

MyBatisTest

    @Test
    public void findUserByIdTest() throws Exception{
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.findUserById(46);
        System.out.println(user);
    }

UserMapper

public interface UserMapper {
    /**
     * 根据id查询用户信息
     *
     * @param id 用户id
     * @return 查询到的用户信息
     */
    User findUserById(Integer id);
}

UserMapper.xml

<?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,一般是接口名称的全类名-->
<mapper namespace="com.by.mapper.UserMapper">
    <!--
      id:和接口方法名保持一致
      resultType:和接口返回类型保持一致
     -->
<!--    单个参数绑定-->
    <select id="findUserById" parameterType="java.lang.Integer" resultType="com.by.pojo.User">
        SELECT * FROM user WHERE id=#{id}
    </select>
</mapper>

6.1.2 序号参数绑定

MyBatisTest

    @Test
    public void findUserByIdAndNameTest() throws Exception{
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.findUserByIdAndName(46,"殷梨亭");
        System.out.println(user);
    }
}

UserMapper

public interface UserMapper {
    /**
     * 根据id和姓名查询用户
     * @param id id
     * @param username 用户名
     * @return 查询到的用户信息
     */
    User findUserByIdAndName(int id, String username);
}

UserMapper.xml

<mapper namespace="com.by.mapper.UserMapper">
<!--    序号参数绑定有两种方式,取其中一种即可,不太推荐使用-->
    
<!--    方式1-->
<!--    arg从零开始往上递增-->    
    <select id="findUserByIdAndName" resultType="com.by.pojo.User">
		SELECT * FROM user WHERE id=#{arg0} AND username=#{arg1}
    </select>
    
    <mapper namespace="com.by.mapper.UserMapper">
        
<!--    方式2-->
<!--    param从1开始往上递增-->
    <select id="findUserByIdAndName" resultType="com.by.pojo.User">
        SELECT * FROM user WHERE id = #{param1} AND username = #{param2} <!--param1 param2 param3 ...-->
    </select>
</mapper>

6.1.3 注解参数绑定

适用于少量参数,SQL语句中参数与注解中的参数保持一致

MyBatisTest

    //注解参数绑定
    @Test
    public void findUserByIdAndNameTest2() throws Exception{
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.findUserByIdAndName2(46,"殷梨亭");
        System.out.println(user);
    }

UserMapper

    /**
     * 根据id和姓名查询用户
     * @param id id
     * @param username 用户名
     * @return 查询到的用户信息
     */
    User findUserByIdAndName2(@Param("id")Integer id,@Param("username") String username);

UserMapper.xml

<!--    注解参数绑定,SQL语句中参数与注解中的参数保持一致即可-->
    <select id="findUserByIdAndName2" resultType="com.by.pojo.User">
        SELECT * FROM user WHERE id = #{id} AND username = #{username}
    </select>

6.1.4 对象参数绑定

适用于多个参数,推荐使用

MyBatisTest

    //对象参数绑定
    @Test
    public void findUserByUserInfoTest() throws Exception{
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User userInfo = new User().setId(46).setUsername("殷梨亭");
        User user = mapper.findUserByUserInfo(userInfo);
        System.out.println(user);
    }

UserMapper

    /**
     * 根据对象信息查询对象
     * @param userInfo 对象信息
     * @return 查询到的对象信息
     */
    User findUserByUserInfo(User userInfo);

UserMapper.xml

<!--    对象参数绑定-->
    <select id="findUserByUserInfo" parameterType="com.by.pojo.User" resultType="com.by.pojo.User">
        SELECT * FROM user WHERE id = #{id} AND username = #{username}
    </select>

6.1.5 Map参数绑定

不推荐使用,就是将对象的属性名和属性值作为键值对放入map中传到方法里

MyBatisTest

    //Map参数绑定
    @Test
    public void findUserByMapTest() throws Exception{
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String, Object> map = new HashMap<>();
        map.put("id",46);
        map.put("username","殷梨亭");
        User user = mapper.findUserByMap(map);
        System.out.println(user);
    }

UserMapper

    /**
     * 利用map进行参数绑定,查询用户信息
     * @param map 存放查询的用户信息
     * @return 查询到的用户信息
     */
    User findUserByMap(Map<String, Object> map);

UserMapper.xml

<!--    Map参数绑定-->
    <select id="findUserByMap" parameterType="java.util.Map" resultType="com.by.pojo.User">
        SELECT * FROM user WHERE id = #{id} AND username = #{username}
    </select>

6.1.6 模糊查询

MyBatisTest

    //模糊查询
    @Test
    public void findUserByNameTest() throws Exception{
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //查询姓张的用户
        List<User> userList = mapper.findUserByName("张");
        userList.forEach(System.out::println);
    }

UserMapper

    /**
     * 根据用户名模糊查询
     * @param username 用户名
     * @return 存储多个用户的集合
     */
    List<User> findUserByName(String username);

UserMapper.xml

<!--    模糊查询-->
    <select id="findUserByName" parameterType="java.lang.String" resultType="com.by.pojo.User">
        <!-- select * from user where username like concat('%',#{username},'%') -->
        <!--SELECT * FROM user WHERE username like '%#{username}%' 错误!!!-->
        select * from user where username like '%${value}%'<!--${}括号中只能是value-->
    </select>

6.1.7 聚合函数查询

对组函数的使用

MyBatisTest

    //聚合函数查询
    @Test
    public void countUserTest() throws Exception{
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Integer count =  mapper.countUser();
        System.out.println("人数为:"+count);
    }

UserMapper

    /**
     * 统计用户数
     * @return 统计的用户数量
     */
    Integer countUser();

UserMapper.xml

<!--   聚合函数 -->
    <select id="countUser" resultType="java.lang.Integer">
        <!--        SELECT count(id) FROM user-->
        SELECT count(1) FROM user
    </select>

6.2 SQL注入

SQL注入(SQL Injection)是一种常见的网络攻击手段,它利用了应用程序对用户输入数据的不安全处理,让攻击者能够向数据库发送恶意的SQL代码。

在了解SQL注入问题之前,先了解一些#{}和${}的区别

6.2.1 #{}和${}的区别

单个简单类型参数类型转换底层sql注入
#{}无限制转换preparedStatement防止
${}必须用value不转换Statement不防止

案例

MyBatisTest

    //#{}和${}的区别
    @Test
    public void getUserByUserNameTest() throws Exception{
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        String username = "张三丰";
        User user = mapper.getUserByUserName(username);
        System.out.println(user);
    }

UserMapper

    /**
     * 根据名字找到该对象信息
     * @param username 用户名
     * @return 用户信息
     */
    User getUserByUserName(String username);

UserMapper.xml

	<!--    #{} 和 ${} 的使用-->
<select id="getUserByUserName" parameterType="java.lang.String"  resultType="com.by.pojo.User">
    <!--    SELECT * FROM user WHERE username= #{username}-->
    <!-- #{} 已经在prepareStatement预编译过了,会发生类型转换,可以防止sql注入-->
    <!--        SELECT * FROM user WHERE username= #{qwersad} 参数可以随便写-->


    <!--SELECT * FROM user WHERE username = ${uesername}
    这是错误的,因为没用经过编译而是直接通过Statement进行拼接,没有调用get方法
    必须填value, 并且value需要用''包裹住,才能进行正常查询,不能防止sql注入问题,
    -->
        select * from user where username = '${value}'
</select>

6.2.1 解决SQL注入问题

用#{}

MyBatisTest

    //SQL注入问题
    @Test
    public void loginTest() throws Exception{
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        String username = "张三丰' #";
        String password = "111";
        User user1 = new User().setUsername(username).setPassword(password);
        User user = mapper.login(user1);
        System.out.println(user);
    }

UserMapper

    /**
     * 登录方法
     * @param user 用户信息
     * @return 查询到的用户对象
     */
    User login(User user);

UserMapper.xml

<!--sql注入问题-->
<select id="login" parameterType="com.by.pojo.User" resultType="com.by.pojo.User" >
    
<!--
	采用'${}'的方法无法防止sql注入问题,因为底层是Statement只是进行字符串的拼接
	如果用户在参数中传入' # 会使后续语句失效
	username= '张三丰' # ' AND password = '111'中,
    AND password = '111'这一串语句就会失效,密码就不会校验,随意登录
-->
<!-- 
    SELECT * FROM user WHERE username='${username}' AND password = '${111}' 
    不可取,有sql注入风险
-->

<!-- 
    当换成#{}的方法时,就不会出现这种问题,因为MyBatis会对参数进行预编译处理,生成安全的SQL查询。	 即使用户名参数包含恶意字符,由于参数值被当作占位符处理并与SQL主体分开,攻击者无法通过修改参数	 来操控SQL结构。因此,同样的恶意输入在使用#{}的情况下,SQL查询会安全地变为:
	SELECT * FROM user WHERE username=? AND password = ?
-->
        SELECT * FROM user WHERE username=#{username} AND password = #{password}

</select>

6.3 删除

MyBatisTest

//删除
@Test
public void deleteUserByIdTest() throws Exception{
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.deleteUserById(48);
    sqlSession.commit();
}

UserMapper

/**
 * 根据id删除用户信息
 * @param id id
 */
void deleteUserById(int id);

UserMapper.xml

<!--    删除-->
<delete id="deleteUserById" parameterType="java.lang.Integer" >
    DELETE FROM user WHERE id = #{id}
</delete>

6.4 修改

MyBatisTest

//修改
@Test
public void updateUserByIdTest() throws Exception{
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Date date = new Date();
    //这里将殷梨亭的信息改成郭靖的信息
    User user = new User().setId(46).setUsername("郭靖").setPassword("111")
            .setBirthday(date).setSex("男").setAddress("湖北省襄阳市");
    mapper.updateUserById(user);
    sqlSession.commit();
}

UserMapper

/**
 * 根据id修改用户信息
 * @param user 存放修改过的用户信息和要修改的id
 */
void updateUserById(User user);

UserMapper.xml

<!--    修改-->
<update id="updateUserById" parameterType="com.by.pojo.User" >
    update
        user
    set
        username=#{username},password=#{password},
        birthday=#{birthday},sex=#{sex},address=#{address}
    where
        id=#{id}
</update>

6.5 添加

MyBatisTest

//添加
@Test
public void insertUserTest() throws Exception{
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Date date = new Date();
    User user = new User().setUsername("张无忌").setPassword("111")
            .setBirthday(date).setSex("男").setAddress("安徽省黄山市");
    mapper.insertUser(user);
    sqlSession.commit();
}

UserMapper

/**
 * 添加用户信息
 * @param user 要添加的用户信息
 */
void insertUser(User user);

UserMapper.xml

<!--    添加-->
<insert id="insertUser" parameterType="com.by.pojo.User">
    INSERT INTO
        user(username,password,birthday,sex,address)
    values
        (#{username},#{password},#{birthday},#{sex},#{address});
</insert>

6.6 主键回填

MyBatisTest

//主键回填
@Test
public void insertUserTest1() throws Exception{
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Date date = new Date();
    User user = new User().setUsername("令狐冲").setPassword("111")
            .setBirthday(date).setSex("男").setAddress("河南省洛阳市");
    mapper.insertUser1(user);
    System.out.println(user.getId());
    sqlSession.commit();
}

UserMapper

    /**
     * 添加用户信息
     * @param user 要添加的用户信息
     */
    void insertUser1(User user);

UserMapper.xml

<!--    主键回填-->
<!--
    useGeneratedKeys=“true”:获取数据库生成的主键
    keyProperty=“id”:主键对应实体类的属性
-->
    <insert id="insertUser1" useGeneratedKeys="true" keyProperty="id" 						parameterType="com.by.pojo.User">
<!--
    主键回填:新增之后,获取新增记录的id值
    keyProperty="id":主键对应实体类的属性
    order="AFTER":先执行插入语句,之后再执行查询语句
    resultType="java.lang.Integer":主键的数据类型
 -->
    <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
        /*查询出刚刚插入的记录自增长id*/
        select last_insert_id();
    </selectKey>

        insert into user(username,password,birthday,sex,address)
        values(#{username},#{password},#{birthday},#{sex},#{address})
    </insert>

7 MyBatis的ORM映射

项目目录:

image-20240611144919030

7.1 思考

MyBatis只能自动维护库表”列名“与”属性名“相同时的对应关系,二者不同时无法自动ORM,如下:

image-20240611142012145

我们可以在查询的时候对相应的字段起别名:

    /**
     * 查询所有用户信息
     * @return 存放用户信息
     */
    public List<Role> findAll();
    <!--列的别名-->
    <select id="findAll" resultType="com.by.pojo.Role">
        select id,role_name as roleName,role_desc as roleDesc from role
    </select>

那如果有很多字段要查询呢,难道每个字段都要起别名吗?

7.2 ORM结果映射

这个时候我们可以使用resultMap标签手动映射,解决属性与表字段不一致的情况

<!--结果映射-->
<!--    id:和select标签的id属性值保存一致
        type:映射实体的全类名
-->
    <resultMap id="findAll2ResultMap" type="com.by.pojo.Role">
<!--
    	描述主键字段的映射关系:
        property:实体类的属性
        column:数据表字段名称
-->
        <id column="id" property="id" />
        <result column="role_name" property="roleName" />
        <result property="roleDesc" column="role_desc" />
    </resultMap>
    <select id="findAll2" resultMap="findAll2ResultMap">
        select * from role
    </select>

8 MyBatis的配置文件

项目结构:

image-20240611150127376

8.1 properties标签

将数据库配置信息放在.properties文件里,例如:db.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3307/mybatis?characterEncoding=UTF-8
jdbc.username=root
jdbc.password=123

存放在properties中如何加载?在mybaties-config.xml文件中用properties标签加载数据库配置文件

<?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>
    <!--引入db.properties-->
    <properties resource="db.properties"></properties>
    <environments default="dev">
        <!-- dev环境 -->
        <environment id="dev">
            <!-- 配置事务的类型:type="JDBC | MANAGED" 两种方式
                 JDBC:表示使用JDBC中原生的事务管理方式
                 MANAGED:被管理,例如Spring
			-->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置连接数据库的信息:用的是数据源(连接池) -->
            <dataSource type="POOLED">
                <!-- mysql5 -->
                <!--使用${}占位符获取配置信息-->
                <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>
        <mappers>
            <package name="com.by.mapper"></package>
        </mappers>
</configuration>

8.2 typeAliases标签

主要是对类起别名,在标签的resultType里直接写别名即可,不用写全类名了

8.2.1 自定义别名

mybaties-config.xml

<typeAliases>
    
    <!--定义单个别名 type是全类名 alies是别名-->
    <!-- <typeAlias type="com.by.pojo.Role" alias="Role"></typeAlias> -->
    
    <!--批量定义别名 直接填整个包-->
    <package name="com.by.pojo"/>
</typeAliases>

RoleMapper.xml

<!--使用别名Role-->
<select id="findAll" resultType="Role">
    select id,role_name as roleName,role_desc as roleDesc from role
</select>

8.2.2 MyBaties默认支持的别名

查看mybatis源码可以看到 Mybatis 默认支持的别名:

image-20240611153906466

统统小写、统统基本类型、集合统统写接口

8.3 Mappers标签

Mappers标签的作用是用来在核心配置文件里面引入映射文件,引入方式有如下三种:

  1. 使用mapper映射文件的路径

    <mappers>
        <mapper resource="com/by/mapper/RoleMapper"/>
    </mappers>
    
  2. 使用mapper接口的路径

    <mappers>
        <mapper class="com.by.mapper.RoleMapper"></mapper>
    </mappers>
    

    注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同

  3. 使用mapper接口的包名批量引入(推荐使用)

    <mappers>
        <package name="com.by.mapper"></package>
    </mappers>
    

    注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同

9 MyBatis的关联查询

项目结构:

image-20240611155338635

9.1 什么是关联查询

  • 实体间的关系(拥有 has、属于 belong)

    • OneToOne:一对一关系(account ←→ user)

    • OneToMany:一对多关系(user ←→ account)

    • ManyToMany:多对多关系(user ←→ role)

  • 什么是关联查询

    当访问关系的一方时,如果需要查看与之关联的另一方数据,则必须使用表链接查询,将查询到的另一方数据,保存在本方的属性中

  • 关联查询的语法

    指定“一方”关系时(对象),使用< association javaType="" >

    指定“多方”关系时(集合),使用< collection ofType="" >

9.2 一对一查询

查询账户信息,关联查询用户信息,一个账户对应一个用户

9.2.1 pojo

Account

package com.by.pojo;

import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
public class Account implements Serializable {

  private Integer id;
  private Integer uid;
  private double money;
  //加入User作为Account的属性
  private User user;
 
}

9.2.2 mapper

AccountMapper

    /**
     * 查询所有账户信息
     * @return 所有的账户信息
     */
    List<Account> findAll();

AccountMapper.xml

<mapper namespace="com.by.mapper.AccountMapper">
    <!--手动映射-->
    <resultMap id="findAllResultMap" type="com.by.pojo.Account">
        <id column="aid" property="id"/>
        <result column="uid" property="uid"/>
        <result column="money" property="money"/>
        <association property="user" javaType="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"/>
        </association>
    </resultMap>

    <select id="findAll" resultMap="findAllResultMap">
        SELECT 
        	a.id aid, a.uid uid, a.money, u.*
        FROM 
        	account a
        left join 
        	user u 
        ON 
        	a.uid = u.id;
    </select>
</mapper>

9.2.3 test

MyBatisTest

//    一对一。一个账户一个用户
@Test
public void testOneToOne() {
    AccountMapper accountMapper = sqlSession.getMapper(AccountMapper.class);
    List<Account> list = accountMapper.findAll();
    list.forEach(System.out::println);
}

9.2.4 测试结果

image-20240611161315169

9.3 一对多

一个用户可以拥有多个账户,根据一个用户查询多个账户.

9.3.1 pojo

User

@Data
@Accessors(chain = true)
public class User {

  private Integer id;
  private String username;
  private String password;
  private java.time.LocalDateTime birthday;
  private String sex;
  private String address;
  //Account作为User的一个属性
  private List<Account> accounts;
}

9.3.2 mapper

UserMapper

    /**
     * 找到所有用户信息
     * @return 查询到的用户信息
     */
    List<User> findAll();

UserMapper.xml

<mapper namespace="com.by.mapper.UserMapper">
	    <!-- 结果映射 -->
    <resultMap id="findAllResultMap" type="com.by.pojo.User">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="birthday" property="birthday"/>
        <result column="sex" property="sex"/>
        <result column="address" property="address"/>
<!-- 
    collection 是用于建立一对多中集合属性的对应关系
    ofType 用于指定集合元素的数据类型
-->
        <collection property="accounts" ofType="com.by.pojo.Account">
            <id column="aid" property="id"/>
            <result column="uid" property="uid"/>
            <result column="money" property="money"/>
        </collection>
    </resultMap>

<select id="findAll" resultMap="findAllResultMap">
    SELECT u.id ,u.username,u.password,u.birthday,u.sex,u.address,a.id aid,a.uid,a.money       FROM user  u left join account a ON u.id = a.uid;
</select>
</mapper>

9.3.3 test

MyBatisTest

//    一对多,一个用户多个账户
@Test
public void testOneToMany() {
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<User> list = userMapper.findAll();
    list.forEach(System.out::println);
}

9.3.3 测试结果

image-20240611163041065

张三丰用户有两个账户id分别是1和3

9.4 多对多查询

一个用户可以有多个角色,一个角色可以有多个用户

9.4.1 pojo

Role

@Data
@Accessors(chain = true)
public class Role {

  private Integer id;
  private String roleName;
  private String roleDesc;
  private List<User> users;
}

9.4.2 mapper

RoleMapper

/**
 * 查询所有角色信息关联用户表
 * @return 存放查询到的用户信息
 */
List<Role> findAll();

RoleMapper.xml

<mapper namespace="com.by.mapper.RoleMapper">

    <resultMap id="findAllResultMap" type="com.by.pojo.Role">
        <id column="rid" property="id"/>
        <result column="role_name" property="roleName"/>
        <result column="role_desc" property="roleDesc"/>
   		<!--本质上和一对多是一样的 -->
        <collection property="users" ofType="com.by.pojo.User">
            <id column="id" property="id"></id>
            <result column="username" property="username"></result>
            <result column="address" property="address"></result>
            <result column="sex" property="sex"></result>
            <result column="birthday" property="birthday"></result>
        </collection>
    </resultMap>

<!--
	这时候就要借助中间表user_role来关联角色表和用户表,但是查询不查中间表,只做连接用。
-->
    <select id="findAll" resultMap="findAllResultMap">
        SELECT
            u.*,r.id rid,r.role_name,r.role_desc
        FROM
            user u
        left join
                user_role ur
        ON
            u.id = ur.uid
        left join
                role r
        ON
            r.id = ur.rid;
    </select>
</mapper>

9.4.3 test

MyBatisTest

    //    多对多,一个用户多个角色,一个角色多个用户
    @Test
    public void testManyToMany() {
        RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
        List<Role> roles =  roleMapper.findAll();
        roles.forEach(System.out::println);
    }

9.4.4 测试结果

image-20240611165126785
未完待续…

  • 28
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值