侧边栏

老冯课堂笔记mybatis

1.框架概述

1.1 什么是框架

程序开发中的框架往往是对常见功能的封装,通常与具体业务无关,也可以认为是软件的半成品。程序框架理解为基础或者机械标准件(例如螺丝螺母标准的机械部件)。

假如你要造一辆马车,在没有框架的情况下,你需要自己去伐木,去把木头做成木板,木棍,然后组成轮子,门,等部件,然后组装起来。但如果你用了框架,就相当于你有现成的轮子,门等部件,你只需要组装一下就可以了。

一个框架是一组可复用的设计构件。

框架(Framework)是整个或者部分系统的可重用设计,是JavaEE底层技术的封装。

框架是可以被开发者定制的应用骨架。

框架是一个半成品,软件是成品。我们在它的基础上开发出成品(软件)。

2.2 框架解决的问题

解决了技术通用的问题

在JavaEE体系中,有着各种各样的技术。不同的软件企业,根据自身的业务需求选择不同的技术,容易造成应用依赖技术,增加了项目开发实现的复杂性和技术风险性。而框架技术就可以解决上述问题。

提升了开发效率

企业项目中使用框架,只需要专注实现业务需求。使用框架的方便性,提升了开发效率。

提升了系统稳定性

一个成熟的框架,经过了在众多企业项目中的验证使用,稳定性有保障。

1676395530809

2.Mybatis框架介绍

mybatis是Apache软件基金会下的一个开源项目,前身是iBatis框架。2010年这个项目由apache 软件基金会迁移到google code下,改名为mybatis。201311月又迁移到了github(GitHub 是一个面向开源及私有 软件项目的托管平台)。

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射(多表)。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。它对 jdbc 的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建 connection、创建 statement、手动设置参数、结果集检索等 jdbc 繁杂的过程代码。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。  

mybatis的优点

  1. 简单易学:mybatis本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个SQL映射文件即可。
  2. 使用灵活:Mybatis不会对应用程序或者数据库的现有设计强加任何影响。SQL语句写在XML里,便于统一管理和优化。
  3. 解除SQL与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易进行单元测试。SQL语句和代码的分离,提高了可维护性。

mybatis的不足

  1. 编写SQL语句时工作量很大,尤其是字段多、关联表多时,更是如此。
  2. SQL语句依赖于数据库,导致数据库移植性差,不能更换数据库。
  3. 框架还是比较简陋,功能尚有缺失。

官方网站及框架包下载

官网地址:http://www.mybatis.org/mybatis-3/

1676395755415

源码和包下载地址:https://github.com/mybatis/mybatis-3/releases

1676395832255

mybatis框架整体架构

1676396099824

1、配置文件
	全局配置文件(核心配置文件):mybatis-config.xml,作用:配置数据源(配置数据库连接信息),引入映射文件
	映射文件:XxMapper.xml,作用:配置sql语句、参数、结果集封装类型等
	
2、SqlSessionFactory
	作用:获取SqlSession
	通过new SqlSessionFactoryBuilder().build(inputStream)来构建,inputStream:读取配置文件的IO流。
	这里涉及到了构建者设计模式

3、SqlSession
	表示会话对象,底层封装的是Connection连接对象
	作用:执行CRUD操作
	
4、Executor
	执行器,SqlSession通过调用它来完成具体的CRUD(使用执行器执行sql语句)
	
5、Mapped Statement
	在映射文件里面配置,包含3部分内容:
		具体的sql,sql执行所需的参数类型,sql执行结果的封装类型
	参数类型和结果集封装类型包括3种:
		HashMap,基本数据类型,pojo

MyBatis的ORM方式

1676396398191

MyBatis的两种映射方式:

  1. 通过XML映射
  2. 通过注解

3.MyBatis框架入门开发

3.1 需求

利用mybatis框架,从MySQL中查询所有的用户

1676396523150

3.2 准备数据

create table user(
	id int primary key auto_increment,
	username varchar(20) not null,
	birthday date,
	sex char(1) default '男',
	address varchar(50)
);

insert into user values(null,'孙悟空','1980-10-24','男','花果山水帘洞');
insert into user values(null,'白骨精','1992-11-12','女','白虎岭白骨洞');
insert into user values(null,'猪八戒','1983-05-20','男','福临山云栈洞');
insert into user values(null,'蜘蛛精','1995-03-22','女','盘丝洞');

3.3 入门案例

准备pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>MybatisDemo</artifactId>
        <groupId>com.darksnow</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>BeginDemo</artifactId>

    <dependencies>
        <!--mybatis核心包-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.0</version>
        </dependency>
        <!--logback日志包-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.26</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.18</version>
        </dependency>
        <!-- 单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

在resources下面创建核心配置文件:mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.darksnow.dao.UserMapper">
    <select id="findAllUsers" resultType="com.darksnow.pojo.User">
        select * from user
    </select>
</mapper>

在resources下导入logback.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
        CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改为 System.err-->
        <target>System.out</target>
        <encoder>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
                %msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level]  %c [%thread] : %msg%n</pattern>
        </encoder>
    </appender>

    <!-- File是输出的方向通向文件的 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!--日志输出路径-->
        <file>E:/code/darksnow-data.log</file>
        <!--指定日志文件拆分和压缩规则-->
        <rollingPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通过指定压缩文件名称,来确定分割文件方式-->
            <fileNamePattern>E:/code/darksnow-data2-%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
    </appender>

    <!--

    level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
   , 默认debug
    <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
    -->
    <root level="ALL">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE" />
    </root>
</configuration>

创建UserMapper接口

package com.darksnow.dao;

import com.darksnow.pojo.User;

import java.util.List;

public interface UserMapper {
    /**
     * 查询所有用户
     */
   List<User> findAllUsers();
}

映射文件(UserMapper.xml)的namespace值 = 接口的全限类名(包.类名)

映射文件(UserMapper.xml)的id值 = 接口中的方法名

image-20230216094425431

创建User实体类

package com.darksnow.pojo;

import java.util.Date;

public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    public User() {
    }

    public User(Integer id, String username, Date birthday, String sex, String address) {
        this.id = id;
        this.username = username;
        this.birthday = birthday;
        this.sex = sex;
        this.address = address;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

编写测试类

package com.darksnow.test;

import com.darksnow.dao.UserMapper;
import com.darksnow.pojo.User;
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.jupiter.api.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;


public class MyBatisTest {

    /**
     * 查询所有用户
     */
    @Test
    public void test01() throws IOException {
        //1.从xml文件中构建SqlSessionFactory
        //定义核心配置文件
        String resource = "mybatis-config.xml";
        //加载核心配置文件,获取输入流
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //2.从SqlSessionFactory中获取SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //3.使用SqlSession获取接口的动态代理对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        //4.使用接口对象调用接口中的方法
        List<User> userList = mapper.findAllUsers();

        //5.遍历集合
        for (User user : userList) {
            System.out.println(user);
        }

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

在mybatis中一个会话相当于一次访问数据库的过程,一个会话对象类似于一个Connection连接对象

1.SqlSessionFactoryBuilder:这是一个临时对象,用完就不需要了,通过这个工厂建造类来创建一个会话工程

2.SqlSessionFactory:可以从一个工厂类中得到一个会话对象,一个项目中只需要创建一个会话工厂对象即可,通过会话工厂对象来创建会话对象。

3.SqlSession:每次访问数据库都需要创建一个会话对象,这个会话对象不能共享,访问完成以后需要关闭会话。

image-20230216104936487

在idea中定义模板,以后方便使用

image-20230216105511562

4.MyBatis的核心配置

mybatis-config.xml,是MyBatis的全局配置文件,包含了全局配置信息。

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
configuration(配置)
	properties(属性)
	settings(设置)
	typeAliases(类型别名)
	typeHandlers(类型处理器)
	objectFactory(对象工厂)
	plugins(插件)
	environments(环境配置)
		environment(环境变量)
			transactionManager(事务管理器)
			dataSource(数据源)
	databaseIdProvider(数据库厂商标识)
	mappers(映射器)

注意了:如果配置多项,必须按照上面的顺序进行配置

4.1 properties(属性)

作用

1.加载外部的properties文件
2.通过子标签property设置属性

案例

使用properties属性,配置数据库连接参数

1.在resources下新建一个jdbc.properties文件,写入一下内容:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql:///darksnow
username=root
password=root

2.引入properties

image-20230216113740810

3.修改数据源

image-20230216113854247

4.2 settings(设置)

settings参数由很多,我们暂时只先讲一个:驼峰匹配(mapUnderscoreToCamelCase),翻译过来是映射下划线到驼峰式命名

image-20230216114433548

作用

image-20230216114656787

驼峰匹配:相当于去掉数据库的列名的下划线,和Java进行匹配

案例

image-20230216115332196

所以此时做查询,虽然查询到了,但是映射不上,那么用户名为null

image-20230216115424399

配置驼峰映射,在mybatis-config.xml做如下配置:

<settings>
        <!--
            mapUnderscoreToCamelCase:驼峰自动映射配置
            数据库字段为:user_name  可以映射到实体属性名 userName
            true代表开启驼峰自动映射
			注意:该配置写在properties下面
        -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

4.3 typeAliases(类型别名)

作用

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

image-20230217083936961

这些全限类名可以通过设置类型别名代替,设置由以下方式:

image-20230217084824946

image-20230217084900612

image-20230217085225325

注意:上图仅截取了部分,剩下的内置类型别名参考网址:https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases

4.4 mappers(映射器)

作用

用来加载Mapper映射文件

用法

image-20230217095202583

5.MyBatis映射文件基本配置

image-20230217102347690

5.1 CRUD操作

映射文件中需要直接书写SQL语句对数据库进行操作,对数据库操作SQL语句主要有CRUD这四类。这四类分别对应映射文件配置中的四类标签:selectinsertupdatedelete

6.动态代理

6.1 需求案例

需求:模拟某企业用户管理业务,需要包含用户登录,用户删除,用户查询功能,并需要统计每个功能的耗时。

package com.darksnow.proxy;

public interface UserService {
    void login();
    void delete();
    void query();
}
package com.darksnow.proxy;

public class UserServiceImpl implements UserService{
    public void login() {
        try {
            long start = System.currentTimeMillis();
            System.out.println("登录");
            Thread.sleep(3000);
            long end = System.currentTimeMillis();
            System.out.println("耗时:" + (end - start));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void delete() {
        try {
            long start = System.currentTimeMillis();
            System.out.println("删除");
            Thread.sleep(4000);
            long end = System.currentTimeMillis();
            System.out.println("耗时:" + (end - start));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void query() {
        try {
            long start = System.currentTimeMillis();
            System.out.println("查询");
            Thread.sleep(5000);
            long end = System.currentTimeMillis();
            System.out.println("耗时:" + (end - start));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package com.darksnow.proxy;

public class Test {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        userService.login();
        userService.delete();
        userService.query();
    }
}

存在什么问题?

业务对象的每个方法都要进行性能统计,存在大量重复代码

6.2 使用动态代理解决问题

代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事,动态代理就是用来对业务功能(方法)进行代理的。

package com.darksnow.proxy;

public interface UserService {
    void login();
    void delete();
    void query();
}
package com.darksnow.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class UserServiceProxyTest {
    public static void main(String[] args) {
        /*
            Proxy类是Java动态代理机制的主类,它提供了用于创建动态代理类和实例的静态方法
            代理通常是基于接口实现的

            public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
            参数解释:
                ClassLoader loader:定义代理类的类加载器
                Class<?>[] interfaces:代理类要实现的接口列表,要求与被代理类的接口一致
                InvocationHandler h:具体实现代理逻辑的接口
            返回值:
                该方法的返回值就是动态生成的代理对象
         */
        UserService userService = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserService.class}, new InvocationHandler() {
            /*
                 public Object invoke(Object proxy, Method method, Object[] args)
                参数解释:
                    Object proxy:就是代理对象(通常不用)
                    Method method:代理对象调用的方法
                    Object[] args:被代理方法中的参数(因为参数个数不确定,所以用一个对象数组表示),如果方法不使用参数,则为null
                返回值:
                    方法被代理后执行的结果
                所有代理对象调用的方法,执行是都会经过invoke
                因此如果要对某个方法进行代理增强,就可以在这个invoke方法中进行定义。
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //获取方法名
                String name = method.getName();
                long start = System.currentTimeMillis();
                if ("login".equals(name)) {
                    System.out.println("登录");
                    Thread.sleep(3000);
                } else if ("delete".equals(name)) {
                    System.out.println("删除");
                    Thread.sleep(4000);
                } else if ("query".equals(name)) {
                    System.out.println("查询");
                    Thread.sleep(5000);
                }
                long end = System.currentTimeMillis();
                System.out.println(name + "耗时:" + (end - start));
                return null;
            }
        });

        userService.login();
        userService.delete();
        userService.query();
    }
}

6.3 动态代理用于增强方法

package com.darksnow.proxy;

public interface UserService {
    void login();
    void delete();
    void query();
}
package com.darksnow.proxy;

public class UserServiceImpl implements UserService{
    public void login() {
        System.out.println("登录操作");
    }

    public void delete() {
        System.out.println("删除操作");
    }

    public void query() {
        System.out.println("查询操作");
    }
}
package com.darksnow.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class InstanceProxy {
    public <T> T istanceProxy(Class<T> clazz, final Object obj){
        /*
            Proxy类是Java动态代理机制的主类,它提供了用于创建动态代理类和实例的静态方法
            代理通常是基于接口实现的

            public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
            参数解释:
                ClassLoader loader:定义代理类的类加载器
                Class<?>[] interfaces:代理类要实现的接口列表,要求与被代理类的接口一致
                InvocationHandler h:具体实现代理逻辑的接口
            返回值:
                该方法的返回值就是动态生成的代理对象
         */
         return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
            /*
                 public Object invoke(Object proxy, Method method, Object[] args)
                参数解释:
                    Object proxy:就是代理对象(通常不用)
                    Method method:代理对象调用的方法
                    Object[] args:被代理方法中的参数(因为参数个数不确定,所以用一个对象数组表示),如果方法不使用参数,则为null
                返回值:
                    方法被代理后执行的结果
                所有代理对象调用的方法,执行是都会经过invoke
                因此如果要对某个方法进行代理增强,就可以在这个invoke方法中进行定义。
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("开启事务");
                method.invoke(obj,args);
                System.out.println("关闭事务");
                return null;
            }
        });

        //Lambda表达式写法
//        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, (proxy,method,args) -> {
//            System.out.println("开启事务");
//            method.invoke(obj,args);
//            System.out.println("关闭事务");
//            return null;
//        });
    }
}
public class UserServiceProxyTest {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        userService.login();

        System.out.println("---------------------------");

        UserService userServiceImpl = new InstanceProxy().istanceProxy(UserService.class, userService);
        userServiceImpl.login();
        System.out.println("---------------------------");
        userServiceImpl.delete();
        System.out.println("---------------------------");
        userServiceImpl.query();
    }
}

7.编写会话工具类

package com.darksnow.utils;

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 java.io.IOException;
import java.io.InputStream;

public class SqlSessionUtil {
    private static SqlSessionFactory factory;

    static {
        //实例化工厂建造类
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        try {
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            factory = builder.build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取sql会话对象
     * @return sql会话对象,自动提交事务
     */
    public static SqlSession getSession(){
        return factory.openSession(true);
    }

    /**
     * 获取sql会话对象
     * @param isAutoCommit  true:自动提交  false:非自动提交
     * @return sql会话对象
     */
    public static SqlSession getSession(boolean isAutoCommit){
        return factory.openSession(isAutoCommit);
    }

    /**
     *  提交事务并关闭session
     */
    public static void commitAndClose(SqlSession sqlSession){
        if (sqlSession != null){
            sqlSession.commit();
            sqlSession.close();
        }
    }


    /**
     * 回滚事务并关闭session
     *
     */
    public static void rollbackAndClose(SqlSession sqlSession){
        if (sqlSession != null) {
            sqlSession.rollback();
            sqlSession.close();
        }
    }
}

8.MyBatis映射文件配置

8.1 parameterType(重要)

在mybatis中传入的参数的数据类型分为两种:

1.简单数据类型:int,string,long,Date...

2.复杂数据类型:类(JavaBean),Map。

说明:如果传递参数是数组或者集合,底层都会封装到Map集合中。

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;

import java.util.List;

public interface UserMapper {
    /**
     * 根据id进行查询
     */
    User queryById(Integer id);
}

配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.darksnow.dao.UserMapper">
    <!--  根据id进行查询  -->
    <!--  
		parameterType="int" 表示sql语句参数id类型,int是Integer的别名
  		MyBatis可以通过类型处理器(TypeHandler),根据接口中的方法
		User queryById(Integer id) 参数类型来推断出具体传入的语句的参数类型
		parameterType在一定程度上可以省略,但是建议都写上。
		parameterType参数类型其实和对应接口参数的类型一致
  	-->
    <select id="queryById" parameterType="int" resultType="User" >
        select * from user where id = #{id}
    </select>

</mapper>

8.2 自增主键回填(了解)

需求:新增一条数据成功后,将这条数据的主键封装到实体类中,并查看主键的值。

	@Test
    public void testSaveUser() {

        User user = new User();
        user.setUserName("蔡徐坤");
        user.setBirthday(new Date());
        user.setSex("男");
        user.setAddress("中国");

        Integer rs = mapper.saveUser(user);

        System.out.println(user.getId());

        sqlSession.commit();

        sqlSession.close();
    }

直接获取id的结果会为null。

有两种方式可以实现添加号数据直接将主键封装到实体类对象中:

方法一

	<!--  添加一个用户  -->
    <!--  #{userName},#{birthday},#{sex},#{address} 参考的 User 这个JavaBean属性名  -->
    <insert id="saveUser" parameterType="User">
        insert into user values(null,#{userName},#{birthday},#{sex},#{address})

        <!--
            keyColumn:主键在表中对应的列名
            keyProperty:主键在实体类中对应的属性名
            resultType:主键的数据类型
            order:
                   BEFORE:先给实体类中的属性名(keyProperty)赋值,然后再执行插入语句
                   AFTER:在添加语句后执行查询主键的语句
        -->
        <selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
            select last_insert_id()
        </selectKey>
    </insert>

方式二

	<!--  添加一个用户  -->
    <!--  #{userName},#{birthday},#{sex},#{address} 参考的 User 这个JavaBean属性名  -->
    <!--
        useGeneratedKeys:true获取自动生成的主键,相当于select last_insert_id()
        keyColumn:主键在表中对应的列名
        keyProperty:主键在实体类中对应的属性名
     -->
    <insert id="saveUser" useGeneratedKeys="true" keyColumn="id" keyProperty="id" parameterType="User">
        insert into user values(null,#{userName},#{birthday},#{sex},#{address})
    </insert>

说明:方式二只适用于支持自动增长主键类型的数据库,比如MySQL,SQL Server。个人推荐使用方式一

8.3 传递单个参数与多个参数(重点)

主要是针对简单类型的数据(int,string,long,date)等进行入参处理

单个参数

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;

import java.util.List;

public interface UserMapper {
    /**
     * 根据id进行查询
     */
    User queryById(Integer id);
}

通过#{参数名}接收

	<select id="queryById" parameterType="int" resultType="User" >
        select * from user where id = #{id}
    </select>

通过#{任意变量名}接收

	<select id="queryById" parameterType="int" resultType="User" >
        select * from user where id = #{darksnow}
    </select>

结论

当接口方法传入一个参数时,mybatis不做特殊处理,#{任意变量名}都可以接收,但是个人建议和名称和接口方法中的参数名一致

多个参数

需求:根据用户名和性别查询用户

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;

import java.util.List;

public interface UserMapper {
    User queryByUserNameAndSex(String userName, String sex);
}

配置文件

	<select id="queryByUserNameAndSex" resultType="User">
        select * from user where user_name = #{username} and sex = #{sex}
    </select>

测试类

	@Test
    public void queryByUserNameAndSex() {
        User user = mapper.queryByUserNameAndSex("孙悟空", "男");
        System.out.println(user);
        sqlSession.close();
    }

结果

image-20230221164508757

解决方案

1.使用参数索引获取:arg0,arg1(了解)

	<select id="queryByUserNameAndSex" resultType="User">
        select * from user where user_name = #{arg0} and sex = #{arg1}
    </select>

2.使用参数位置获取:param1,param2(了解)

	<select id="queryByUserNameAndSex" resultType="User">
        select * from user where user_name = #{param1} and sex = #{param2}
    </select>

3.使用命名参数获取,明确指定传入参数的名称(重要)

步骤一:在接口中传入参数通过@Param指定参数名称

package com.darksnow.dao;

import com.darksnow.pojo.User;

import java.util.List;

public interface UserMapper {
    User queryByUserNameAndSex(@Param("username") String userName, @Param("sex") String sex);
}

步骤二:在接收参数时,通过指定的名称获取参数值

	<select id="queryByUserNameAndSex" resultType="User">
        select * from user where user_name = #{username} and sex = #{sex}
    </select>

8.4 pojo参数(重点)

使用pojo来传递参数(JavaBean)

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface UserMapper {
    Integer saveUser(User user);
}

映射文件

	<insert id="saveUser" parameterType="User">
        insert into user values(null,#{userName},#{birthday},#{sex},#{address})
    </insert>

测试类

	@Test
    public void testSaveUser() {

        User user = new User();
        user.setUserName("蔡徐坤");
        user.setBirthday(new Date());
        user.setSex("男");
        user.setAddress("中国");

        Integer rs = mapper.saveUser(user);

        sqlSession.commit();

        sqlSession.close();
    }

8.5 HashMap参数

需求:模拟用户登录,登录方法参数是Map集合,泛型都是String类型,分别表示用户名和性别

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface UserMapper {
    User login(Map<String,String> map);
}

配置文件

	<select id="login" resultType="User">
        select * from user where user_name=#{username} and sex=#{sex}
    </select>

测试类

	@Test
    public void login() {
        Map<String, String> map = new HashMap<>();
        map.put("username","孙悟空");
        map.put("sex","男");
        User user = mapper.login(map);
        System.out.println(user);
    }

注意

参数map中key要与SQL语句中#{}的取值名称一致!!!

8.6 参数的获取

参数值的获取指的是,sql语句获取方法中传入的参数,有两种方式:#{},${}

#{}与${}取值的后台处理的区别

#{}取值

image-20230221174558208

image-20230221174657746

${取值}

image-20230221174734037

image-20230221175117074

image-20230221175237243

${}取值的应用场景

在一些特殊的应用场景中,需要对SQL语句部分(不是参数)进行拼接,这个时候就需要使用${}来进行拼接,比如:

1.假设企业开发中随着数据量的增大,往往会将数据表按照年份进行分表,比如:
2023_user,2024_user,...... 对这些表进行查询就需要动态的把年份传入进来,而年份是表名的一部分,并不是参数,JDBC无法对其进行预编译,所以就只能使用${}进行拼接。
	select * from ${year}_user;

2.根据表名查询数据总条数
	select count(*) from ${tableName};

总结来说:如果需要涉及到sql中的不是查询条件,只能使用${}拼接

需求:查询指定表中的总记录数

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface UserMapper {
    int selectCountByTableName(@Param("tableName") String table);
}

配置文件

	<select id="selectCountByTableName" resultType="int">
        select count(*) from ${tableName}
    </select>

测试类

	@Test
    public void selectCountByTableName() {
        int rs = mapper.selectCountByTableName("user");
        System.out.println(rs);
        sqlSession.close();
    }

8.7 ${}取值注意事项

${}获取单个值

获取单个值时,最好通过命名参数的形式获取,如果不指定参数,也可以使用${value}来获取传入参数

image-20230222084750659

image-20230222084840491

8.8 模糊查询

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface UserMapper {
    //需求:模糊查询地址中含有 山 的用户
    List<User> queryUsersByAddressLike(@Param("address") String address);
}

配置文件

	 <!--
        需求:模糊查询地址中含有 山 的用户
        1.select * from user where address like '%#{address}%' 这里时拼接,不能使用#{},必须使用${}
          select * from user where address like '%${address}%'  =>  select * from user where address like '%山%'
          但是我们发现存在SQL注入问题
        2.我们实际开发中必须考虑SQL注入问题,而且还必须实现拼接,可以使用mysql自带的拼接函数:concat
          select * from user where address like concat('%',#{address},'%')
        3.select * from user where address like "%"#{address}"%"

		#{},防止sql注入
		${},不能防止sql注入
    -->
    <select id="queryUsersByAddressLike" resultType="User">
        select * from user where address like "%"#{address}"%"
    </select>

测试类

	@Test
    public void queryUsersByAddressLike() {
        List<User> list = mapper.queryUsersByAddressLike("山");
        System.out.println(list);
        sqlSession.close();
    }

8.9 结果映射

在使用原生的JDBC操作时,对于结果集ResultSet,需要手动处理,mybatis框架提供了resultType和resultMap来对结果集进行封装。

resultType

1.返回值是简单类型

例如:int,string,...

image-20230222103440826

2.返回值为一个pojo对象时

image-20230222103605000

image-20230222103704539

3.返回值为List时

当返回值为List集合时,resultType需要设置成集合中存储的具体的JavaBean对象数据类型(泛型)

image-20230222103832450

image-20230222103919878

4.返回值为Map时(一条结果)

接口方法

package com.darksnow.dao;

import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface UserMapper {
    //需求:查询id是1的数据,将查询的结果封装到Map<String,Object>中
    Map<String,Object> selectByIdReturnMap(Integer id);
}

配置文件

	<select id="selectByIdReturnMap" resultType="Map">
        select * from user where id = #{id}
    </select>

测试方法

	@Test
    public void selectByIdReturnMap() {
        Map<String, Object> map = mapper.selectByIdReturnMap(1);
        System.out.println(map);
        sqlSession.close();
    }

测试结果

{birthday=1980-10-24, address=花果山水帘洞, user_name=孙悟空, sex=男, id=1}

通过上述结果我们发现如果返回一条数据存放到Map中,那么表中的列名会作为Map集合的key,结果作为Map集合的value

5.返回值为Map时(多条结果)

接口方法

package com.darksnow.dao;

import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface UserMapper {
 //需求:查询user表中的所有数据并封装到Map<String,User>集合中
 //需要在接口的方法上使用注解@MapKey指定数据表中哪一列作为Map集合的key,否则mybatis不知道具体哪个列作为Map集合的key
    @MapKey("id")
    Map<String,User> selectReturnMap();
}

配置文件

	<select id="selectReturnMap" resultType="Map">
        select * from user
    </select>

测试方法

	@Test
    public void selectReturnMap() {
        Map<String, User> map = mapper.selectReturnMap();
        System.out.println(map);
        sqlSession.close();
    }

测试结果

{
    16={birthday=2023-02-21, address=中国, user_name=蔡徐坤, sex=男, id=16}, 
	1={birthday=1980-10-24, address=花果山水帘洞, user_name=孙悟空, sex=男, id=1}, 
	2={birthday=1992-11-12, address=白虎岭白骨洞, user_name=白骨精, sex=女, id=2}, 
	3={birthday=1983-05-20, address=福临山云栈洞, user_name=猪八戒, sex=男, id=3}, 
	4={birthday=1995-03-22, address=盘丝洞, user_name=蜘蛛精, sex=女, id=4}
}

resultMap

resultMap可以解决两大问题:

1.pojo属性名和表结构字段名不一致的问题

2.可以完成高级查询,比如说:一对一,一对多,多对多(后面会讲)

需求:使用resultMap完成结果集的封装

1.将驼峰命名注释掉,注释掉之后我们的数据库表字段名为user_name,但是JavaBean的属性为userName,此时数据无法封装,现在尝试使用resultMap来解决

2.配置resultMap

	<!--
        resultMap标签上的属性
            id:resultMap标签的唯一标识,不能重复,一般使用被引用
            type:结果集的封装类型,这里是封装到User
            autoMapping:操作单表时,不配置默认为true,如果JavaBean对象中的属性名和表中字段名相同,则自动映射
    -->
    <resultMap id="userResultMap" type="User" autoMapping="true">
        <!--
            配置主键映射关系
            column 数据表的字段名
            property JavaBean中的属性名
        -->
        <id column="id" property="id"/>
        <!--
            配置指定字段与属性的映射关系
            column 数据表的字段名
            property JavaBean中的属性名
        -->
        <result column="user_name" property="userName"/>
    </resultMap>
    <select id="queryById" parameterType="int" resultMap="userResultMap" >
        select * from user where id = ${id}
    </select>

3.接口方法

package com.darksnow.dao;

import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface UserMapper {
    User queryById(@Param("id") Integer id);
}

4.测试方法

	@Test
    public void testQueryById() throws IOException {
        User user = mapper.queryById(1);
        System.out.println(user);
        sqlSession.close();
    }

8.10 动态SQL

ognl表达式
	o1 or o2 满足一个即可
	o1 and o2 都得满足
	o1 == o2,o1 eq o2  判断是否相等
	o1 != o2,o1 neq o2 不相等
	o1 lt o2  小于
	o1 lte o2 小于等于,gt(大于),gte(大于等于)
	o1 in o2
	o1 not in o2
	o1 + o2   (- * / %)
	!o1,not o1  非,取反
	o1.方法  调用对象方法
	o1.属性  调用对象属性
	o1[index]  根据索引取值

if标签

格式:
<if test="判断条件">
	满足条件执行的代码
</if>

说明:
1.if标签:判断语句,用于进行逻辑判断的,如果判断条件为true,则执行if标签的文本内容
2.test属性:用来填写表达式,支持ognl表达式

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface UserMapper {
    //根据用户名进行模糊查询,且性别必须为男
    List<User> queryLikeUserName(@Param("userName") String userName);
}

配置文件

	<resultMap id="userResultMap" type="User" autoMapping="true">
        <id column="id" property="id"/>
        <result column="user_name" property="userName"/>
    </resultMap>
	<select id="queryLikeUserName" resultMap="userResultMap">
        select * from user where sex = '男'
        <if test="userName != null and userName.trim() != ''">
            and user_name like "%"#{userName}"%"
        </if>
    </select>

测试方法

	@Test
    public void queryLikeUserName() {
        List<User> list = mapper.queryLikeUserName(null);
        System.out.println(list);
        sqlSession.close();
    }

choose,when,otherwise标签

choose标签:分支选择(多选一,遇到成立条件即停止)
when子标签:编写条件,不管有多少个when条件,一旦其中一个条件成立,后面when条件都不执行。
otherwise子标签:当所有条件都不满足时,才会执行该条件。
test属性:编写ognl表达式

需求

编写一个查询方法,设置两个参数,一个用户名,一个地址

根据用户名或者地址查询所有男性用户:
	如果输入了用户名则按照用户名模糊查询
	否则就按照地址查询,两个条件只能成立一个
	如果都不输入就查询用户名为“孙悟空”的用户
	
分析:
	查询所有男性用户,如果输入了用户名则按照用户名模糊查询
		select * from user where sex = "男" and user_name like "%"#{username}"%";
	查询所有男性用户,如果输入了地址则按照地址查询
    	select * from user where sex = "男" and address = #{address};
    查询所有男性用户,如果都不输入就查询用户名为“孙悟空”的用户
    	select * from user where sex = "男" and user_name = "孙悟空";

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface UserMapper {
    List<User> queryByUserNameOrAddress(@Param("username") String username,@Param("address") String address);
}

配置文件

	<resultMap id="userResultMap" type="User" autoMapping="true">
        <id column="id" property="id"/>
        <result column="user_name" property="userName"/>
    </resultMap>
	<select id="queryByUserNameOrAddress" resultMap="userResultMap">
        select * from user where sex = "男"
        <choose>
            <when test="username != null and username.trim() != ''">
                and user_name like "%"#{username}"%"
            </when>
            <when test="address != null and address.trim() != ''">
                and address = #{address}
            </when>
            <otherwise>
                and user_name = "孙悟空"
            </otherwise>
        </choose>
    </select>

测试方法

	@Test
    public void queryByUserNameOrAddress() {
        List<User> list = mapper.queryByUserNameOrAddress("", "中国");
        System.out.println(list);
        sqlSession.close();
    }

where标签

where标签:拼接多条查询时,能够添加where标签,可以去除多余的and或者or

需求:按照下面的条件查询所有用户
	如果输入了用户名按照用户名进行查询
		select * from user where user_name = #{username}
	如果输入了地址按照地址进行查询
		select * from user where address = #{address}
	如果两者都输入,两个条件都要成立
		select * from user where user_name = #{username} and address = #{address}

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface UserMapper {
    List<User> queryByUserNameOrAddress(@Param("username") String username,@Param("address") String address);
}

配置文件

<!-- <select id="queryByUserNameAndAddress" resultMap="userResultMap">
        select * from user where
        <if test="username != null and username.trim() != ''">
            user_name = #{username}
        </if>
        <if test="address != null and address.trim() != ''">
            address = #{address}
        </if>
    </select> -->
    <!--
        上面的写法会出问题,可以使用where标签来解决
        因为它或去掉多余的and或者or
        但是得在前面!!!
    -->
	<resultMap id="userResultMap" type="User" autoMapping="true">
        <id column="id" property="id"/>
        <result column="user_name" property="userName"/>
    </resultMap>
    <select id="queryByUserNameAndAddress" resultMap="userResultMap">
        select * from user
        <where>
            <if test="username != null and username.trim() != ''">
                user_name = #{username}
            </if>
            <if test="address != null and address.trim() != ''">
                and address = #{address}
            </if>
        </where>
    </select>

测试方法

	@Test
    public void queryByUserNameAndAddress() {
        List<User> list = mapper.queryByUserNameAndAddress("蔡徐坤", "中国");
        System.out.println(list);
        sqlSession.close();
    }

set标签

set标签:在update语句中,可以添加一个set标签,会将动态sql最后多余的逗号去掉。

需求:修改用户的信息,如果参数user中的某个属性为null,则不修改

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface UserMapper {
    void updateSelectiveUser(User user);
}

配置文件

	<update id="updateSelectiveUser">
        update user
        <set>
            <if test="userName != null and userName.trim() != ''">
                user_name = #{userName},
            </if>
            <if test="birthday != null">
                birthday = #{birthday},
            </if>
            <if test="sex != null and sex.trim() != ''">
                sex = #{sex},
            </if>
            <if test="address != null and address.trim() != ''">
                address = #{address}
            </if>
        </set>
        where id = #{id}
    </update>

测试方法

	@Test
    public void updateSelectiveUser() {
        User user = new User();
        user.setId(16);
        user.setUserName("爱坤坤");
        user.setSex("女");
        user.setBirthday(new Date());
        user.setAddress("");

        mapper.updateSelectiveUser(user);
        sqlSession.commit();
        sqlSession.close();
    }

foreach标签

foreach标签:遍历集合或者数组

<foreach collection="集合名或者数组名" item="元素" separator="分隔符" open="以什么开始" close="以什么结束">
     #{id}
</foreach>

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface UserMapper {
    //这里一定要加@Param
    List<User> queryByIds(@Param("arrIds") Integer[] arrIds);
}

配置文件

	<resultMap id="userResultMap" type="User" autoMapping="true">
        <id column="id" property="id"/>
        <result column="user_name" property="userName"/>
    </resultMap>
	<select id="queryByIds" resultMap="userResultMap">
        select * from user where id in
        <foreach collection="arrIds" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </select>

SQL片段

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.darksnow.dao.UserMapper">

    <!--
        SQL片段
        可以把重复的sql语句提出来,需要时引用即可
        id:唯一标识
        文本:sql语句
    -->
    <sql id="select_user">select * from user</sql>

	<resultMap id="userResultMap" type="User" autoMapping="true">
        <id column="id" property="id"/>
        <result column="user_name" property="userName"/>
    </resultMap>
    
    <select id="queryByIds" resultMap="userResultMap">
        <!--   关联使用sql片段     -->
        <include refid="select_user"/>
        where id in
        <foreach collection="arrIds" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </select>
</mapper>

9.多表查询

9.0 准备SQL

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for tb_item
-- ----------------------------
DROP TABLE IF EXISTS `tb_item`;
CREATE TABLE `tb_item` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `item_name` varchar(32) NOT NULL COMMENT '商品名称',
  `item_price` float(6,1) NOT NULL COMMENT '商品价格',
  `item_detail` text COMMENT '商品描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of tb_item
-- ----------------------------
INSERT INTO `tb_item` VALUES ('1', 'iPhone 6', '5288.0', '苹果公司新发布的手机产品。');
INSERT INTO `tb_item` VALUES ('2', 'iPhone 6 plus', '6288.0', '苹果公司发布的新大屏手机。');


-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(100) DEFAULT NULL COMMENT '用户名',
  `password` varchar(100) DEFAULT NULL COMMENT '密码',
  `name` varchar(100) DEFAULT NULL COMMENT '姓名',
  `age` int(10) DEFAULT NULL COMMENT '年龄',
  `sex` int(11) DEFAULT NULL COMMENT '0-女 1-男',
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES ('1', 'zhangsan', '123456', '张三', '30', '1');
INSERT INTO `tb_user` VALUES ('2', 'lisi', '123456', '李四', '21', '0');
INSERT INTO `tb_user` VALUES ('3', 'wangwu', '123456', '王五', '22', '1');
INSERT INTO `tb_user` VALUES ('4', 'zhangwei', '123456', '张伟', '20', '1');
INSERT INTO `tb_user` VALUES ('5', 'lina', '123456', '李娜', '28', '0');
INSERT INTO `tb_user` VALUES ('6', '蔡徐坤', '123', '小菜', '18', '1');

-- ----------------------------
-- Table structure for tb_order
-- ----------------------------
DROP TABLE IF EXISTS `tb_order`;
CREATE TABLE `tb_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL,
  `order_number` varchar(20) NOT NULL COMMENT '订单号',
  PRIMARY KEY (`id`),
  KEY `FK_orders_1` (`user_id`),
  CONSTRAINT `FK_orders_1` FOREIGN KEY (`user_id`) REFERENCES `tb_user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of tb_order
-- ----------------------------
INSERT INTO `tb_order` VALUES ('1', '1', '20140921001');
INSERT INTO `tb_order` VALUES ('2', '2', '20140921002');
INSERT INTO `tb_order` VALUES ('3', '1', '20140921003');

-- ----------------------------
-- Table structure for tb_orderdetail
-- ----------------------------
DROP TABLE IF EXISTS `tb_orderdetail`;
CREATE TABLE `tb_orderdetail` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_id` int(32) DEFAULT NULL COMMENT '订单号',
  `item_id` int(32) DEFAULT NULL COMMENT '商品id',
  `total_price` double(20,0) DEFAULT NULL COMMENT '商品总价',
  `status` int(11) DEFAULT NULL COMMENT '状态',
  PRIMARY KEY (`id`),
  KEY `FK_orderdetail_1` (`order_id`),
  KEY `FK_orderdetail_2` (`item_id`),
  CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`order_id`) REFERENCES `tb_order` (`id`),
  CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`item_id`) REFERENCES `tb_item` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of tb_orderdetail
-- ----------------------------
INSERT INTO `tb_orderdetail` VALUES ('1', '1', '1', '5288', '1');
INSERT INTO `tb_orderdetail` VALUES ('2', '1', '2', '6288', '1');
INSERT INTO `tb_orderdetail` VALUES ('3', '2', '2', '6288', '1');
INSERT INTO `tb_orderdetail` VALUES ('4', '3', '1', '5288', '1');

9.1 表介绍和关系介绍

tb_user:用户表
tb_order:订单表
tb_item:商品表
tb_orderdetail:订单详情表

tb_user与tb_order表关系:
	用户表 <==> 订单表  一对多,一个人可以下多个订单
	订单表 <==> 用户表  一对一,一个订单只能属于一个人
	总结:tb_user和tb_order属于一对多的关系,需要将一的一方tb_user的主键作为多的一方tb_order的外键维护关系
	
tb_order与tb_item表关系:
	订单表 <==> 商品表:一个订单可以有多个商品
	商品表 <==> 订单表:一个商品可以在多个订单上
	总结:tb_order和tb_1item属于多对多的关系,需要创建中间表tb_orderdetail维护两表的关系,并且将两表的主键作为中间
		 表的外键。

9.2 一对一查询

需求:通过订单编号20140921003查询出订单信息,并查询出下单人信息。

一个订单编号对应一个订单,一个订单只能属于一个人,所以上述需求为一对一的实现。

接口

package com.darksnow.dao;

import com.darksnow.pojo.Order;
import org.apache.ibatis.annotations.Param;

public interface OrderMapper {
    //根据订单号查询订单以及下单人的信息
    Order queryOrderAndUserByOrderNumber(@Param("orderNumber") String orderNumber);
}

实体类

package com.darksnow.pojo;

public class Order {
    private Integer id;
    private String orderNumber;

    //关联User对象   tb_order.user_id(外键) 引用 tb_user.id(主键)
    private User user;

    //省略getter,setter,toString方法
}
package com.darksnow.pojo;

import java.util.Date;

public class User {
    private Integer id;
    private String userName;
    private String password;
    private String name;
    private Integer age;
    private Integer sex;

    //省略getter,setter,toString方法
}

配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.darksnow.dao.OrderMapper">

    <resultMap id="OrderResultMap" type="Order" autoMapping="true">
        <id column="oid" property="id"/>
        <result column="order_number" property="orderNumber"/>
        <!--
            一对一映射关系使用子标签association来表示引用另外一个pojo类的对象,这里引用的是User对象
            property="user"  表示在Order类中的引用的User类的对象成员变量名
            javaType="User"  表示引用的user对象属于User类型
            大白话:Order对象中引用的是User对象(javaType),其属性名叫user(property)
        -->
        <association property="user" javaType="User" autoMapping="true">
            <!--  下面是关于user表中的属性映射   -->
            <id column="uid" property="id"/>
            <result column="user_name" property="userName"/>
        </association>
    </resultMap>
    <select id="queryOrderAndUserByOrderNumber" resultMap="OrderResultMap">
        select
            tbo.id as oid,
            tbo.order_number,
            tbu.id as uid,
            tbu.user_name,
            tbu.password,
            tbu.name,
            tbu.age,
            tbu.sex
        from
            tb_order tbo
        inner join
            tb_user tbu
        on
            tbo.user_id = tbu.id
        where
            tbo.order_number = #{orderNumber}
    </select>
</mapper>

测试方法

	@Test
    public void queryOrderAndUserByOrderNumber() {
        SqlSession session = SqlSessionUtil.getSession();
        OrderMapper mapper = session.getMapper(OrderMapper.class);
        Order order = mapper.queryOrderAndUserByOrderNumber("20140921003");
        System.out.println(order);
        SqlSessionUtil.commitAndClose(session);
    }

9.3 一对多查询

需求:查询id为1的用户及其订单信息

一个用户可以有多个订单,一个订单只能属于一个用户,用户(1) <==> 订单(n)

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.Param;


public interface UserMapper {
   //根据用户id查询用户及其订单信息
   User oneToManyQuery(@Param("id") Integer id);
}

实体类

package com.darksnow.pojo;

import java.util.List;

public class User {
    private Integer id;
    private String userName;
    private String password;
    private String name;
    private Integer age;
    private Integer sex;

    //一个用户可以对应多个订单
    private List<Order> orders;

   //省略getter setter toString方法
}

配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.darksnow.dao.UserMapper">
    <resultMap id="oneToManyResultMap" type="User" autoMapping="true">
        <id column="uid" property="id"/>
        <result column="user_name" property="userName"/>
        <!--
            一对多映射关系使用子标签collection来表示引用多的一方的pojo类的对象,这里引用的是Order对象
            property="orders"  表示在User类中的引用的List<Order>的对象成员变量名
            javaType="List"  表示引用的List<Order>对象属于List类型
            大白话:User对象中引用的是List<Order>对象(javaType),其属性名叫orders(property)
            ofType="Order" 表示List集合中存储的元素的数据类型
        -->
        <collection property="orders" javaType="List" ofType="Order" autoMapping="true">
            <id column="oid" property="id"/>
            <result column="order_number" property="orderNumber"/>
        </collection>
    </resultMap>
    <select id="oneToManyQuery" resultMap="oneToManyResultMap">
        select
            tbu.id as uid,
            tbu.user_name,
            tbu.password,
            tbu.name,
            tbu.age,
            tbu.sex,
            tbo.id as oid,
            tbo.order_number
        from
            tb_user tbu
        inner join
            tb_order tbo
        on
            tbu.id = tbo.user_id
        where
            tbu.id = 1;
    </select>
</mapper>

测试方法

	@Test
    public void oneToManyQuery() {
        SqlSession session = SqlSessionUtil.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        User user = mapper.oneToManyQuery(1);
        System.out.println(user);
        SqlSessionUtil.commitAndClose(session);
    }

9.4 多对多查询

需求:查询订单号为20140921001的订单详情信息,订单详情信息 = 订单 + 商品

关系:一个订单对应多个订单详情,一个订单详情对应一种商品

实体类

package com.darksnow.pojo;

import java.util.List;

public class Order {
    private Integer id;
    private String orderNumber;

    //关联User对象   tb_order.user_id(外键) 引用 tb_user.id(主键)
    private User user;

    //一个订单包含多个订单详情信息
    private List<Orderdetail> detailList;

  	//省略getter,setter,toString
}
package com.darksnow.pojo;

public class Orderdetail {
    private Integer id;
    private Double totalPrice;
    private Integer status;

    //一个订单详情包含一个商品
    private Item item;

	//省略getter,setter,toString
}
package com.darksnow.pojo;

public class Item {
    private Integer id;
    private String item_name;
    private Double item_price;
    private String item_detail;

 	//省略getter,setter,toString
}

接口

package com.darksnow.dao;

import com.darksnow.pojo.Order;
import org.apache.ibatis.annotations.Param;

public interface OrderMapper {
    Order queryOrderAndDetailByOrderNumber(@Param("ordeNumber") String orderNumber);
}

配置文件

	<resultMap id="orderAndDetailMap" type="Order" autoMapping="true">
        <id property="id" column="oid"/>
        <result property="orderNumber" column="order_number"/>
        <!--  多个订单详情 detailList -->
        <collection property="detailList" javaType="List" ofType="Orderdetail" autoMapping="true">
            <id property="id" column="detailId"/>
            <result property="totalPrice" column="total_price"/>
            <!--  一个商品   -->
            <association property="item" javaType="Item" autoMapping="true">
                <id property="id" column="itemId"/>
            </association>
        </collection>
    </resultMap>
    <select id="queryOrderAndDetailByOrderNumber" resultMap="orderAndDetailMap">
        select
        tbo.id as oid,
        tbo.order_number,
        detail.id as detailId,
        detail.total_price,
        detail.status,
        item.id as itemId,
        item.item_name,
        item.item_price,
        item.item_detail
        from
        tb_order tbo
        inner join tb_orderdetail detail on tbo.id = detail.order_id
        inner join tb_item item on detail.item_id = item.id
        where
        tbo.order_number = #{ordeNumber}
    </select>

测试方法

	@Test
    public void queryOrderAndDetailByOrderNumber() {
        SqlSession session = SqlSessionUtil.getSession();
        OrderMapper mapper = session.getMapper(OrderMapper.class);
        Order order = mapper.queryOrderAndDetailByOrderNumber("20140921001");
        System.out.println(order);
        SqlSessionUtil.commitAndClose(session);
    }

结果

Order{
    id=1, 
    orderNumber='20140921001', 
    user=null, 
    detailList=[
    	Orderdetail{
    		id=1, 
    		totalPrice=5288.0, 
    		status=1, 
    		item=Item{
    			id=1, 
    			item_name='iPhone 6', 
    			item_price=5288.0, 
    			item_detail='苹果公司新发布的手机产品。'
			}
		}, 
		Orderdetail{
           	id=2, 
            totalPrice=6288.0, 
            status=1, 
            item=Item{
            	id=2, 
            	item_name='iPhone 6 plus', 
            	item_price=6288.0, 
            	item_detail='苹果公司发布的新大屏手机。'
        	}
		}
	]
}

9.5 多对多扩展

需求:根据订单编号20140921001,查询订单信息,查询订单所属用户信息,订单中的商品详细信息(在原来的9.5笔记的基础上多了个所属用户信息)

接口

package com.darksnow.dao;

import com.darksnow.pojo.Order;
import org.apache.ibatis.annotations.Param;

public interface OrderMapper {
    Order queryOderAndDetailAndUserByOrderNumber(@Param("ordeNumber") String orderNumber);
}

配置文件

	<resultMap id="orderAndDetailAndUserMap" type="Order" autoMapping="true" >
        <id property="id" column="oid"/>
        <result property="orderNumber" column="order_number"/>

        <!--   Oder-User :一对一关联   -->
        <association property="user" javaType="User" autoMapping="true">
            <id property="id" column="uid"/>
            <result property="userName" column="user_name"/>
        </association>

        <!--  多个订单详情 detailList -->
        <collection property="detailList" javaType="List" ofType="Orderdetail" autoMapping="true">
            <id property="id" column="detailId"/>
            <result property="totalPrice" column="total_price"/>
            <!--  一个商品   -->
            <association property="item" javaType="Item" autoMapping="true">
                <id property="id" column="itemId"/>
            </association>
        </collection>
    </resultMap>
    <select id="queryOderAndDetailAndUserByOrderNumber" resultMap="orderAndDetailAndUserMap">
        select
        tbo.id as oid,
        tbo.order_number,
        detail.id as detailId,
        detail.total_price,
        detail.status,
        item.id as itemId,
        item.item_name,
        item.item_price,
        item.item_detail,
        tbu.id as uid,
        tbu.user_name,
        tbu.password,
        tbu.name,
        tbu.age,
        tbu.sex
        from
        tb_order tbo
        inner join tb_orderdetail detail on tbo.id = detail.order_id
        inner join tb_item item on detail.item_id = item.id
        inner join tb_user tbu on tbu.id = tbo.user_id
        where
        tbo.order_number = #{ordeNumber}
    </select>

image-20230227100547773

测试方法

	@Test
    public void queryOderAndDetailAndUserByOrderNumber() {
        SqlSession session = SqlSessionUtil.getSession();
        OrderMapper mapper = session.getMapper(OrderMapper.class);
        Order order = mapper.queryOderAndDetailAndUserByOrderNumber("20140921001");
        System.out.println(order);
        SqlSessionUtil.commitAndClose(session);
    }

结果

Order{
    id=1,
    orderNumber='20140921001',
    user=User{
        id=1,
        userName='zhangsan',
        password='123456',
        name='张三',
        age=30,
        sex=1,
        orders=null
    }, 
    detailList=[
        Orderdetail{
            id=1,
            totalPrice=5288.0,
            status=1,
            item=Item{
                id=1,
                item_name='iPhone 6',
                item_price=5288.0,
                item_detail='苹果公司新发布的手机产品。'
            }
        },
        Orderdetail{
            id=2,
            totalPrice=6288.0,
            status=1,
            item=Item{
                id=2,
                item_name='iPhone 6 plus',
                item_price=6288.0,
                item_detail='苹果公司发布的新大屏手机。'
            }
        }
    ]
}

10.注解开发

10.1 注解实现CRUD

开启驼峰命名!!!

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.*;

import java.util.List;


public interface UserMapper {
   //1.新增数据
   @Insert("insert into tb_user values(null,#{userName},#{password},#{name},#{age},#{sex})")
   void saveUser(User user);

   //2.根据id删除用户
   @Delete("delete from tb_user where id = #{id}")
   void deleteUserById(Integer id);

   //3.修改id为6的用户数据
   @Update("update tb_user set user_name=#{userName},age=#{age} where id = #{id}")
   void updateUser(User user);

   //4.查询所有用户数据
   @Select("select * from tb_user")
   List<User> queryAllUsers();
}

测试方法

package com.darksnow.test;

import com.darksnow.dao.UserMapper;
import com.darksnow.pojo.User;
import com.darksnow.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Test;

import java.util.List;

public class AnnotationTest {
    @Test
    public void saveUser() {
        SqlSession session = SqlSessionUtil.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        User user = new User();
        user.setUserName("坤坤");
        user.setPassword("123456");
        user.setName("爱坤");
        user.setAge(18);
        user.setSex(1);
        mapper.saveUser(user);
        SqlSessionUtil.commitAndClose(session);
    }

    @Test
    public void deleteUserById() {
        SqlSession session = SqlSessionUtil.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        mapper.deleteUserById(14);
        SqlSessionUtil.commitAndClose(session);
    }

    @Test
    public void updateUser() {
        SqlSession session = SqlSessionUtil.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        User user = new User();
        user.setId(6);
        user.setUserName("坤坤");
        user.setAge(20);
        mapper.updateUser(user);
        SqlSessionUtil.commitAndClose(session);
    }

    @Test
    public void queryAllUsers() {
        SqlSession session = SqlSessionUtil.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        List<User> users = mapper.queryAllUsers();
        System.out.println(users);
        SqlSessionUtil.commitAndClose(session);
    }
}

10.2 返回新增数据的id(自增主键回填)

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.*;

import java.util.List;


public interface UserMapper {
   @Insert("insert into tb_user values(null,#{userName},#{password},#{name},#{age},#{sex})")
   /*
      useGeneratedKeys:true获取自动生成的主键
      keyColumn:主键在表中对应的列名
      keyProperty:主键在实体类中对应的属性名
   */
   @Options(useGeneratedKeys = true,keyColumn = "id",keyProperty = "id")
   void saveUser(User user);
}

测试方法

	@Test
    public void saveUser() {
        SqlSession session = SqlSessionUtil.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        User user = new User();
        user.setUserName("爱坤坤");
        user.setPassword("123456");
        user.setName("爱坤");
        user.setAge(18);
        user.setSex(1);
        mapper.saveUser(user);
        System.out.println(user.getId());
        SqlSessionUtil.commitAndClose(session);
    }

10.3 注解实现别名映射

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.*;

import java.util.List;


public interface UserMapper {
   @Select("select * from tb_user")
   @Results(value = {
           @Result(column = "user_name",property = "userName")
   })
   void saveUser(User user);
}

测试方法

	@Test
    public void queryAllUsers() {
        SqlSession session = SqlSessionUtil.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        List<User> users = mapper.queryAllUsers();
        System.out.println(users);
        SqlSessionUtil.commitAndClose(session);
    }

10.4 注解实现动态SQL

方式一

ProviderUtil

package com.darksnow.utils;

import org.apache.ibatis.annotations.Param;

public class ProviderUtil {
    //如果用户名为空,那么只根据性别查询,如果用户名不为空,那么根据性别查询的同时,根据用户名进行模糊查询
    public String queryUsersBySexOrUsernameSQL(@Param("sex") Integer sex, @Param("username") String username){
        String sql = "select * from tb_user where sex = #{sex}";
        if (username != null && !"".equals(username)) {
            sql += " and user_name like concat('%',#{username},'%')";
        }
        return sql;
    }
}

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;
import com.darksnow.utils.ProviderUtil;
import org.apache.ibatis.annotations.*;

import java.util.List;


public interface UserMapper {
   /*
      type:填写处理的类的字节码文件对象
      method:具体的处理方法
    */
   @SelectProvider(type = ProviderUtil.class,method = "queryUsersBySexOrUsernameSQL")
   @Results(value = {
           @Result(column = "user_name",property = "userName")
   })
   List<User> queryUsersBySexOrUsername(@Param("sex") Integer sex,@Param("username") String username);
}

测试方法

	@Test
    public void queryUsersBySexOrUsername() {
        SqlSession session = SqlSessionUtil.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        List<User> users = mapper.queryUsersBySexOrUsername(1,"");
        System.out.println(users);
        SqlSessionUtil.commitAndClose(session);
    }

方式二

ProviderUtil

package com.darksnow.utils;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.jdbc.SQL;

public class ProviderUtil {
    //如果用户名为空,那么只根据性别查询,如果用户名不为空,那么根据性别查询的同时,根据用户名进行模糊查询
    public String queryUsersBySexOrUsernameSQL(@Param("sex") Integer sex, @Param("username") String username){
        SQL sql = new SQL();
        sql.SELECT("*").FROM("tb_user").WHERE("sex = #{sex}");
        if (username != null && !"".equals(username)) {
            sql.WHERE("user_name like concat('%',#{username},'%')");
        }
        return sql.toString();
    }
}

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;
import com.darksnow.utils.ProviderUtil;
import org.apache.ibatis.annotations.*;

import java.util.List;


public interface UserMapper {
   /*
      type:填写处理的类的字节码文件对象
      method:具体的处理方法
    */
   @SelectProvider(type = ProviderUtil.class,method = "queryUsersBySexOrUsernameSQL")
   @Results(value = {
           @Result(column = "user_name",property = "userName")
   })
   List<User> queryUsersBySexOrUsername(@Param("sex") Integer sex,@Param("username") String username);
}

测试方法

	@Test
    public void queryUsersBySexOrUsername() {
        SqlSession session = SqlSessionUtil.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        List<User> users = mapper.queryUsersBySexOrUsername(1,"坤");
        System.out.println(users);
        SqlSessionUtil.commitAndClose(session);
    }

10.5 注解实现一对一

需求:查询订单编号20140921003的订单信息

接口

package com.darksnow.dao;

import com.darksnow.pojo.Order;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.*;

public interface OrderMapper {
    @Select(value = "select * from tb_order where order_number = #{orderNumber}")
    @Results(value = {
            //id = true,表示id是主键
            @Result(column = "id",property = "id",id = true),
            @Result(column = "order_number",property = "orderNumber"),
            /*
                column = "user_id" user_id相当于一个外键,引入另外一方主键
                property = "user" 关联对象的属性名,在Order类中有一个属性(User user),查询的结果封装到user
                javaType = User.class  返回的对象类型,这里给的是字节码文件对象
                one:一对一配置,使用注解@One中的select属性引入另一条sql语句所在的接口
             */
            @Result(column = "user_id",property = "user",
                    javaType = User.class,one = @One(select = "com.darksnow.dao.UserMapper.findById"))
    })
    Order queryOneToOne(@Param("orderNumber") String orderNumber);
}
package com.darksnow.dao;

import com.darksnow.pojo.User;
import com.darksnow.utils.ProviderUtil;
import org.apache.ibatis.annotations.*;

import java.util.List;


public interface UserMapper {
   @Select("select * from tb_user where id = #{id}")
   User findById(@Param("id") Integer id);
}

测试方法

	@Test
    public void queryOneToOne(){
        SqlSession session = SqlSessionUtil.getSession();
        OrderMapper mapper = session.getMapper(OrderMapper.class);
        Order order = mapper.queryOneToOne("20140921003");
        System.out.println(order);
        SqlSessionUtil.commitAndClose(session);
    }

10.6 注解实现一对多

需求:查询id为1的用户及其订单信息

接口

package com.darksnow.dao;

import com.darksnow.pojo.User;
import com.darksnow.utils.ProviderUtil;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface UserMapper {
   @Select("select * from tb_user where id = #{id}")
   @Results({
           @Result(column = "id",property = "id",id = true),
           @Result(column = "user_name",property = "userName"),
           @Result(column = "id",property = "orders",
                   javaType = List.class,many = @Many(select = "com.darksnow.dao.OrderMapper.findByUserId"))
   })
   User queryOneToMany(@Param("id") Integer id);
}
package com.darksnow.dao;

import com.darksnow.pojo.Order;
import com.darksnow.pojo.User;
import org.apache.ibatis.annotations.*;

public interface OrderMapper {
    @Select("select * from tb_order where user_id = #{userId}")
    @Results({
         @Result(column = "order_number",property = "orderNumber")
    })
    Order findByUserId(@Param("userId") Integer id);
}

测试方法

	@Test
    public void queryOneToMany(){
        SqlSession session = SqlSessionUtil.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        User user = mapper.queryOneToMany(1);
        System.out.println(user);
        SqlSessionUtil.commitAndClose(session);
    }

PS:以后快面试的时候会去讲MyBatis的延迟加载,MyBatis的缓存

posted @   lkjlwq  阅读(49)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css
点击右上角即可分享
微信分享提示