【Mybatis】源码分析-自定义框架

1、自定义持久层框架

1.1、分析JDBC操作问题

package blnp.net.cn.jvm.demos;

import java.sql.*;

/**
 * <p></p>
 *
 * @author lyb 2045165565@qq.com
 * @createDate 2024/5/24 14:24
 */
public class JdbcTest {

    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            // 加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 通过驱动管理类获取数据库链接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_test?characterEncoding=utf-8", "root", "root");
            // 定义sql语句?表示占位符
            String sql = "select * from heros where name = ?";
            // 获取预处理statement
            preparedStatement = connection.prepareStatement(sql);
            // 设置参数,第 1⼀个参数为sql语句中参数的序号(从1开始),第⼆1 个参数为设置的参数值
            preparedStatement.setString(1, "张飞");
            // 向数据库发出sql执⾏查询,查询出结果集
            resultSet = preparedStatement.executeQuery();

            // 遍历查询结果集
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String hpMax = resultSet.getString("hp_max");
                // 封装 Hero 对象
                //user.setId(id);
                //user.setUsername(username);
                System.out.println("hpMax = " + hpMax);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

JDBC问题总结:

        使用原始JDBC开发会存在以下这些问题:

  1. 数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能。
  2. Sql语句在代码中硬编码,造成代码不易维护,实际应⽤中sql变化的可能较⼤,sql变动需要改变java代码。
  3. 使⽤ preparedStatement 向占有位符号传参数存在硬编码,因为sql语句的 where 条件不⼀定,可能多也可能少,修改sql还要修改代码,系统不易维护。
  4. 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析⽐较⽅便

1.2、问题解决的思路

  • 使⽤数据库连接池初始化连接资源
  • 将sql语句抽取到xml配置⽂件中
  • 使⽤反射、内省等底层技术,⾃动将实体与表进⾏属性与字段的⾃动映射

1.3、自定义框架设计步骤

1.3.1、使用端

        提供核心配置文件:

  • sqlMapConfig.xml : 存放数据源信息
  • 引⼊mapper.xml
  • Mapper.xml : sql语句的配置⽂件信息

1.3.2、框架端

1、读取配置文件

        读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可以创建 javaBean 来存储。

  1. Configuration : 存放数据库基本信息、Map<唯⼀标识,Mapper> 唯⼀标识:namespace + "." + id
  2. MappedStatement:sql语句、statement类型、输⼊参数java类型、输出参数java类型
2、解析配置文件

        创建sqlSessionFactoryBuilder类,使用⽅法:sqlSessionFactory build()。

  1. 使⽤dom4j解析配置⽂件,将解析出来的内容封装到Configuration和MappedStatement中
  2. 创建SqlSessionFactory的实现类DefaultSqlSession
3、创建 SqlSessionFactory

        通过⽅法:openSession() : 获取sqlSession接⼝的实现类实例对象

4、创建sqlSession接⼝及实现类:主要封装crud⽅法
//查询所有
selectList(String statementId,Object param)

//查询单个
selectOne(String statementId,Object param)

1.4、自定义框架实现

1.4.1、使用端配置

        首先创建一个Maven工程,使用JDK8.引用的依赖如下文件所示。最终使用端创建的相关文件结构可以见下图:

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.blnp.net</groupId>
    <artifactId>mybatisPersistence</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.17</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
    </dependencies>

</project>

        接着创建 sqlMapConfig.xml 文件,保存关于数据库的相关链接信息以及Mapper映射文件的路径信息。

<configuration>
    <!--数据库连接信息-->
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    <property name="jdbcUrl" value="jdbc:mysql:///my_test"></property>
    <property name="user" value="root"></property>
    <property name="password" value="admin@123"></property>

    <!--引入sql配置信息 -->
    <mapper resource="HerosMapper.xml"></mapper>
</configuration>

        接着创建我们测试使用的数据库表对象实体 HerosEntity:

package com.blnp.net.entity;

import java.io.Serializable;
import java.util.Date;

public class HerosEntity implements Serializable {
    private Integer id;

    private String name;

    private Double hpMax;

    private Double hpGrowth;

    private Double hpStart;

    private Double mpMax;

    private Double mpGrowth;

    private Double mpStart;

    private Double attackMax;

    private Double attackGrowth;

    private Double attackStart;

    private Double defenseMax;

    private Double defenseGrowth;

    private Double defenseStart;

    private Double hp5sMax;

    private Double hp5sGrowth;

    private Double hp5sStart;

    private Double mp5sMax;

    private Double mp5sGrowth;

    private Double mp5sStart;

    private Double attackSpeedMax;

    private String attackRange;

    private String roleMain;

    private String roleAssist;

    private Date birthdate;

    private static final long serialVersionUID = 1L;

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name == null ? null : name.trim();
    }

    public Double getHpMax() {
        return hpMax;
    }

    public void setHpMax(Double hpMax) {
        this.hpMax = hpMax;
    }

    public Double getHpGrowth() {
        return hpGrowth;
    }

    public void setHpGrowth(Double hpGrowth) {
        this.hpGrowth = hpGrowth;
    }

    public Double getHpStart() {
        return hpStart;
    }

    public void setHpStart(Double hpStart) {
        this.hpStart = hpStart;
    }

    public Double getMpMax() {
        return mpMax;
    }

    public void setMpMax(Double mpMax) {
        this.mpMax = mpMax;
    }

    public Double getMpGrowth() {
        return mpGrowth;
    }

    public void setMpGrowth(Double mpGrowth) {
        this.mpGrowth = mpGrowth;
    }

    public Double getMpStart() {
        return mpStart;
    }

    public void setMpStart(Double mpStart) {
        this.mpStart = mpStart;
    }

    public Double getAttackMax() {
        return attackMax;
    }

    public void setAttackMax(Double attackMax) {
        this.attackMax = attackMax;
    }

    public Double getAttackGrowth() {
        return attackGrowth;
    }

    public void setAttackGrowth(Double attackGrowth) {
        this.attackGrowth = attackGrowth;
    }

    public Double getAttackStart() {
        return attackStart;
    }

    public void setAttackStart(Double attackStart) {
        this.attackStart = attackStart;
    }

    public Double getDefenseMax() {
        return defenseMax;
    }

    public void setDefenseMax(Double defenseMax) {
        this.defenseMax = defenseMax;
    }

    public Double getDefenseGrowth() {
        return defenseGrowth;
    }

    public void setDefenseGrowth(Double defenseGrowth) {
        this.defenseGrowth = defenseGrowth;
    }

    public Double getDefenseStart() {
        return defenseStart;
    }

    public void setDefenseStart(Double defenseStart) {
        this.defenseStart = defenseStart;
    }

    public Double getHp5sMax() {
        return hp5sMax;
    }

    public void setHp5sMax(Double hp5sMax) {
        this.hp5sMax = hp5sMax;
    }

    public Double getHp5sGrowth() {
        return hp5sGrowth;
    }

    public void setHp5sGrowth(Double hp5sGrowth) {
        this.hp5sGrowth = hp5sGrowth;
    }

    public Double getHp5sStart() {
        return hp5sStart;
    }

    public void setHp5sStart(Double hp5sStart) {
        this.hp5sStart = hp5sStart;
    }

    public Double getMp5sMax() {
        return mp5sMax;
    }

    public void setMp5sMax(Double mp5sMax) {
        this.mp5sMax = mp5sMax;
    }

    public Double getMp5sGrowth() {
        return mp5sGrowth;
    }

    public void setMp5sGrowth(Double mp5sGrowth) {
        this.mp5sGrowth = mp5sGrowth;
    }

    public Double getMp5sStart() {
        return mp5sStart;
    }

    public void setMp5sStart(Double mp5sStart) {
        this.mp5sStart = mp5sStart;
    }

    public Double getAttackSpeedMax() {
        return attackSpeedMax;
    }

    public void setAttackSpeedMax(Double attackSpeedMax) {
        this.attackSpeedMax = attackSpeedMax;
    }

    public String getAttackRange() {
        return attackRange;
    }

    public void setAttackRange(String attackRange) {
        this.attackRange = attackRange == null ? null : attackRange.trim();
    }

    public String getRoleMain() {
        return roleMain;
    }

    public void setRoleMain(String roleMain) {
        this.roleMain = roleMain == null ? null : roleMain.trim();
    }

    public String getRoleAssist() {
        return roleAssist;
    }

    public void setRoleAssist(String roleAssist) {
        this.roleAssist = roleAssist == null ? null : roleAssist.trim();
    }

    public Date getBirthdate() {
        return birthdate;
    }

    public void setBirthdate(Date birthdate) {
        this.birthdate = birthdate;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", id=").append(id);
        sb.append(", name=").append(name);
        sb.append(", hpMax=").append(hpMax);
        sb.append(", hpGrowth=").append(hpGrowth);
        sb.append(", hpStart=").append(hpStart);
        sb.append(", mpMax=").append(mpMax);
        sb.append(", mpGrowth=").append(mpGrowth);
        sb.append(", mpStart=").append(mpStart);
        sb.append(", attackMax=").append(attackMax);
        sb.append(", attackGrowth=").append(attackGrowth);
        sb.append(", attackStart=").append(attackStart);
        sb.append(", defenseMax=").append(defenseMax);
        sb.append(", defenseGrowth=").append(defenseGrowth);
        sb.append(", defenseStart=").append(defenseStart);
        sb.append(", hp5sMax=").append(hp5sMax);
        sb.append(", hp5sGrowth=").append(hp5sGrowth);
        sb.append(", hp5sStart=").append(hp5sStart);
        sb.append(", mp5sMax=").append(mp5sMax);
        sb.append(", mp5sGrowth=").append(mp5sGrowth);
        sb.append(", mp5sStart=").append(mp5sStart);
        sb.append(", attackSpeedMax=").append(attackSpeedMax);
        sb.append(", attackRange=").append(attackRange);
        sb.append(", roleMain=").append(roleMain);
        sb.append(", roleAssist=").append(roleAssist);
        sb.append(", birthdate=").append(birthdate);
        sb.append("]");
        return sb.toString();
    }
}

        最后,创建我们实体映射文件以及使用的SQL语句 HerosMapper.xml :

<mapper namespace="Heros">
    <!--SQL的唯一标识:由 namespace.id 加上 statementId 来组成并定位到语句-->

    <!--查询所有-->
    <select id="selectAll" resultType="com.blnp.net.entity.HerosEntity">
        select * from heros
    </select>

    <!--查询单个-->
    <select id="selectOne" paramterType="com.blnp.net.entity.HerosEntity" resultType="com.blnp.net.entity.HerosEntity">
        select * from heros where id=#{id} and name=#{name}
    </select>
</mapper>

1.4.2、框架端配置

1、准备工作 & 加载配置文件

        这边我们新建一个空白的Maven工程,并创建一个 Resources 类。完整代码如下所示:

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.blnp.net</groupId>
    <artifactId>persistenceFrame</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>
package com.blnp.net.io;

import java.io.InputStream;

/**
 * <p>框架资源加载类</p>
 *
 * @author lyb 2045165565@qq.com
 * @createDate 2024/5/24 16:51
 */
public class Resources {

    /**
     * 用途:指定文件路径获取字节流
     *  根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
     * @author liaoyibin
     * @date 16:54 2024/5/24
     * @params [path]
     * @param path
     **/
    public static InputStream getResourceAsStream(String path) {
        //跟进当前类的类加载器进行获取
        InputStream resourceAsStream = Resources.class
                .getClassLoader()
                .getResourceAsStream(path);
        return resourceAsStream;
    }
}

        随后将该工程进行打包输出:

        接着,继续在我们之前的使用端工程里引入该自定义持久框架工程的包。并编写如下测试代码:

2、创建容器对象

创建 MappedStatement 容器对象

package com.blnp.net.pojo;

/**
 * <p>MappedStatement容器对象</p>
 *
 * @author lyb 2045165565@qq.com
 * @createDate 2024/5/24 17:17
 */
public class MappedStatement {

    /**
     *  id标识
     **/
    private String id;

    /**
     *  返回值类型
     **/
    private Class<?> resultType;

    /**
     *  参数值类型
     **/
    private Class<?> paramType;

    /**
     *  sql语句
     **/
    private String sql;

    public String getId() {
        return id;
    }

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

    public Class<?> getResultType() {
        return resultType;
    }

    public void setResultType(Class<?> resultType) {
        this.resultType = resultType;
    }

    public Class<?> getParamType() {
        return paramType;
    }

    public void setParamType(Class<?> paramType) {
        this.paramType = paramType;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }
}

创建数据库连接信息容器对象

package com.blnp.net.pojo;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>数据库连接信息容器对象</p>
 *
 * @author lyb 2045165565@qq.com
 * @createDate 2024/5/24 17:25
 */
public class Configuration {

    /**
     *  数据库连接配置信息
     **/
    private DataSource dataSource;

    /**
     *  mapper文件中的查询语句信息
     **/
    private Map<String,MappedStatement> mappedStatementMap = new HashMap<>();

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Map<String, MappedStatement> getMappedStatementMap() {
        return mappedStatementMap;
    }

    public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
        this.mappedStatementMap = mappedStatementMap;
    }
}
3、解析核心配置文件 sqlMapConfig

1、首先创建以下接口类:

package com.blnp.net.sqlSession;

/**
 * <p>数据库连接会话接口</p>
 *
 * @author lyb 2045165565@qq.com
 * @createDate 2024/5/24 17:36
 */
public interface SqlSessionFactory {

}

2、接着创建文件解析类

package com.blnp.net.config;

import com.blnp.net.io.Resources;
import com.blnp.net.pojo.Configuration;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

/**
 * <p>文件解析助手</p>
 *
 * @author lyb 2045165565@qq.com
 * @createDate 2024/5/24 17:45
 */
public class XmlConfigBuilder {

    /**
     *  自定义配置
     **/
    private Configuration configuration;

    public XmlConfigBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    /**
     * 用途:sqlMapConfig.xml 转为 javabean 对象
     * @author liaoyibin
     * @date 18:00 2024/5/24
     * @params [inputStream]
     * @param inputStream
     **/
    public Configuration parseConfiguration(InputStream inputStream) throws DocumentException,PropertyVetoException,ClassNotFoundException {
        //将字节流解析为文档对象
        Document document = new SAXReader().read(inputStream);
        //获取根元素对象
        Element rootElement = document.getRootElement();

        //获取文档中根节点内所有的 property 标签对象
        List<Element> propertyElements = rootElement.selectNodes("//property");
        Properties properties = new Properties();

        //迭代获取数据
        for (Element propertyElement : propertyElements) {
            String name = propertyElement.attributeValue("name");
            String value = propertyElement.attributeValue("value");
            properties.setProperty(name,value);
        }

        /**
         *  连接池信息解析读取
         **/
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        //获取驱动类信息
        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
        //获取JDBC连接地址
        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        //获取数据库连接用户密码
        comboPooledDataSource.setUser(properties.getProperty("username"));
        comboPooledDataSource.setPassword(properties.getProperty("password"));

        //填充configuration
        configuration.setDataSource(comboPooledDataSource);

        /**
         *  mapper部分信息解析读取
         **/
        List<Element> mapperElements = rootElement.selectNodes("//mapper");
        XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration);

        //迭代解析mapper
        for (Element mapperElement : mapperElements) {
            //mapper文件路径
            String mapperPath = mapperElement.attributeValue("resource");
            //获取当前mapper的字节流对象
            InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);
            //解析对应对象
            xmlMapperBuilder.parse(resourceAsStream);
        }

        return configuration;
    }
}
4、解析映射配置文件mapper
package com.blnp.net.config;

import com.blnp.net.pojo.Configuration;
import com.blnp.net.pojo.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

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

/**
 * <p>xml mapper映射文件解析读取</p>
 *
 * @author lyb 2045165565@qq.com
 * @createDate 2024/5/24 18:00
 */
public class XmlMapperBuilder {

    private Configuration configuration;

    public XmlMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    /**
     * 用途:Mapper映射文件解析读取
     * @author liaoyibin
     * @date 18:13 2024/5/24
     * @params [inputStream]
     * @param inputStream
     **/
    public void parse(InputStream inputStream) throws DocumentException,ClassNotFoundException {
        //流对象解析处理
        Document document = new SAXReader().read(inputStream);
        //获取根节点对象
        Element rootElement = document.getRootElement();

        //获取 Mapper 文件的命名空间
        String namespace = rootElement.attributeValue("namespace");
        //获取 Mapper 文件的所有查询语句的 Statement
        List<Element> select = rootElement.selectNodes("//select");

        //迭代解析处理
        for (Element element : select) {
            //即查询语句的ID
            String id = element.attributeValue("id");
            //请求参数
            String paramType = element.attributeValue("paramType");
            //响应参数
            String resultType = element.attributeValue("resultType");

            //反射获取入参对象
            Class<?> paramTypeClass = getClassType(paramType);
            //返回结果class
            Class<?> resultTypeClass = getClassType(resultType);
            //statementId
            String key = namespace + "." + id;
            //sql语句
            String textTrim = element.getTextTrim();

            /**
             *  封装 mappedStatement
             **/
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setResultType(resultTypeClass);
            mappedStatement.setParamType(paramTypeClass);
            mappedStatement.setSql(textTrim);

            //填充 configuration
            configuration.getMappedStatementMap().put(key, mappedStatement);
        }
    }

    /**
     * 用途:根据类全限定名进行反射获取对象实例
     * @author liaoyibin
     * @date 18:10 2024/5/24
     * @params [paramType]
     * @param paramType
     **/
    private Class<?> getClassType (String paramType) throws ClassNotFoundException {
        if (!Optional.ofNullable(paramType).isPresent()) {
            return null;
        }
        Class<?> aClass = Class.forName(paramType);
        return aClass;
    }
}
5、会话对象sqlSession处理
package com.blnp.net.sqlSession;

import com.blnp.net.config.XmlConfigBuilder;
import com.blnp.net.pojo.Configuration;
import com.blnp.net.sqlSession.defaults.DefaultSqlSessionFactory;
import org.dom4j.DocumentException;

import java.beans.PropertyVetoException;
import java.io.InputStream;

/**
 * <p>sqlMapConfig.xml 文件解析类</p>
 *
 * @author lyb 2045165565@qq.com
 * @createDate 2024/5/24 17:40
 */
public class SqlSessionFactoryBuilder {

    private Configuration configuration;

    public SqlSessionFactoryBuilder() {
        this.configuration = new Configuration();
    }

    public SqlSessionFactory build(InputStream inputStream) throws DocumentException,PropertyVetoException,ClassNotFoundException {
        /**
         *  第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
         **/
        //1.解析配置⽂件,封装Configuration
        XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(configuration);
        //解析处理
        Configuration configuration = xmlConfigBuilder.parseConfiguration(inputStream);

        /**
         *  第二:创建sqlSessionFactory对象:工厂类:生产sqlSession:会话对象
         **/
        SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return sqlSessionFactory;
    }
}
6、解析实现与结果封装
package com.blnp.net.sqlSession.defaults;

import com.blnp.net.config.BoundSql;
import com.blnp.net.pojo.Configuration;
import com.blnp.net.pojo.MappedStatement;
import com.blnp.net.pojo.ParameterMapping;
import com.blnp.net.sqlSession.Executor;
import com.blnp.net.utils.ColumnUtil;
import com.blnp.net.utils.GenericTokenParser;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * <p>mapper代理对象生成实现</p>
 *
 * @author lyb 2045165565@qq.com
 * @createDate 2024/5/27 9:33
 */
public class SimpleExecutor implements Executor {

    /**
     *  数据库连接
     **/
    private Connection connection = null;

    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception {
        // 1.注册驱动,获取连接
        connection = configuration.getDataSource().getConnection();

        // 2.获取sql语句 eg: select * from user where id = #{id} and username = #{username}
        //转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储
        String sql = mappedStatement.getSql();

        //对SQL进行处理
        BoundSql boundsql = getBoundSql(sql);
        //获取处理后的SQL
        String finalSql = boundsql.getSqlText();
        //获取传⼊参数类型
        Class<?> paramType = mappedStatement.getParamType();
        //3.获取预编译preparedStatement对象
        PreparedStatement preparedStatement = connection.prepareStatement(finalSql);

        //获取传入参数列表
        List<ParameterMapping> parameterMappingList = boundsql.getParameterMappingList();

        //4.设置参数
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            //获取SQL内执行条件字段的名称
            String name = parameterMapping.getContent();
            //反射获取对应字段信息
            Field declaredField = paramType.getDeclaredField(name);
            //防止字段是私有的,开启暴力访问
            declaredField.setAccessible(true);
            //参数的值
            Object o = declaredField.get(params[0]);
            //给占位符赋值
            preparedStatement.setObject(i + 1, o);
        }
        //5.执行结果解析处理
        ResultSet resultSet = preparedStatement.executeQuery();
        //取得待执行SQL语句的输出响应参数定义信息
        Class<?> resultType = mappedStatement.getResultType();
        ArrayList<Object> results = new ArrayList<Object>();

        while (resultSet.next()) {
            //获取元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            Object o = resultType.newInstance();
            //总列数
            int columnCount = metaData.getColumnCount();

            for (int i = 1; i <= columnCount; i++) {
                //属性名
                String columnName = metaData.getColumnName(i);
                //属性值
                Object value = resultSet.getObject(columnName);
                //对属性名进行驼峰转换
                String fieldName = ColumnUtil.lineToHump(columnName);
                //创建属性描述器,为属性⽣成读写⽅法
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(fieldName, resultType);
                //获取写方法
                Method writeMethod = propertyDescriptor.getWriteMethod();
                //获取设置值的参数类型
                String paramTypeName = writeMethod.getParameterTypes()[0].getName();
                //根据参数类型进行转换处理
                Object paramVal = switchParam(paramTypeName, value);
                //向类中写⼊值
                writeMethod.invoke(o, paramVal);
            }
            results.add(o);
        }
        return (List<E>) results;
    }

    /**
     * 用途:SQL解析标记
     * @author liaoyibin
     * @date 9:55 2024/5/27
     * @params [sql]
     * @param sql
     **/
    private BoundSql getBoundSql(String sql) {
        //标记处理类:主要是配合通⽤标记解析器GenericTokenParser类完成对配置⽂件等的解析⼯作,其中TokenHandler主要完成处理
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        //GenericTokenParser :通⽤的标记解析器,完成了代码⽚段中的占位符的解析,然后再根据给定的标记处理器(TokenHandler)来进⾏表达式的处理
        //三个参数:分别为openToken (开始标记)、closeToken (结束标记)、handler (标记处理器)
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        String parse = genericTokenParser.parse(sql);
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

        BoundSql boundSql = new BoundSql(parse, parameterMappings);
        return boundSql;
    }

    /**
     * 用途:参数类型转换
     * @author liaoyibin
     * @date 19:16 2024/5/27
     * @params [paramName, val]
     * @param paramName
     * @param val
     **/
    private Object switchParam(String paramName,Object val) {
        if (!Optional.ofNullable(paramName).isPresent()) {
            return null;
        }
        if (Objects.equals(paramName,Integer.class.getName())) {
            return Integer.valueOf(val+"");
        }
        else if (Objects.equals(paramName,Double.class.getName())) {
            return Double.valueOf(val+"");
        }
        else if (Objects.equals(paramName, String.class.getName())) {
            return String.valueOf(val);
        }
        else if (Objects.equals(paramName, Date.class.getName())) {
            return new Date();
        }
        else {
            return null;
        }
    }

    @Override
    public void close() throws SQLException {
        connection.close();
    }
}
7、集成自定义框架测试
package com.blnp.net.demo;

import com.blnp.net.entity.HerosEntity;
import com.blnp.net.io.Resources;
import com.blnp.net.sqlSession.SqlSession;
import com.blnp.net.sqlSession.SqlSessionFactory;
import com.blnp.net.sqlSession.SqlSessionFactoryBuilder;
import org.dom4j.DocumentException;
import org.junit.Test;

import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;

/**
 * <p></p>
 *
 * @author lyb 2045165565@qq.com
 * @createDate 2024/5/24 16:57
 */
public class IPersistenceTest {

  public static void main(String[] args) throws Exception {
      InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
      SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
      SqlSessionFactory sqlSessionFactory = factoryBuilder.build(resourceAsStream);
      SqlSession sqlSession = sqlSessionFactory.openSession();

      HerosEntity hero = new HerosEntity();
      hero.setId(10005);
      hero.setName("亚瑟");

      HerosEntity result = sqlSession.selectOne("Heros.selectOne", hero);
      System.out.println("result = " + result.toString());
  }

  @Test
  public void selectAll() throws Exception {
      InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
      SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
      SqlSessionFactory sqlSessionFactory = factoryBuilder.build(resourceAsStream);
      SqlSession sqlSession = sqlSessionFactory.openSession();

      List<HerosEntity> objects = sqlSession.selectList("Heros.selectAll");
    for (HerosEntity object : objects) {
      System.out.println("object = " + object.toString());
    }
  }
}

查询所有方法:

8、源码下载

1、自定义持久层框架源码:persistenceFrame.7z - 蓝奏云

2、测试程序:mybatisPersistence.7z - 蓝奏云

1.4.3、常见问题

  • 比如数据库查询返回的字段名,驼峰问题或下划线问题
  • 比如实体类字段参数类型问题
  • 其它问题

1.5、自定义框架优化

        通过自定义持久层框架后,我们解决了JDBC操作数据库带来的⼀些问题:例如频繁创建释放数据库连接,硬编码,⼿动封装返回结果集等问题。但是我们自定义的框架同样也带来了一些问题:

  1. dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调⽤sqlsession⽅法,关闭 sqlsession)
  2. dao的实现类中存在硬编码,调⽤sqlsession的⽅法时,参数statement的id硬编码

解决方法:使⽤代理模式来创建接⼝的代理对象在sqlSession中添加⽅法实

@Test
public void test2() throws Exception {
	InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml")
	SqlSessionFactory build = new
	SqlSessionFactoryBuilder().build(resourceAsSteam);
	SqlSession sqlSession = build.openSession();
	
	User user = new User();
	user.setld(l);
	user.setUsername("tom");
	
	//代理对象
	UserMapper userMapper = sqlSession.getMappper(UserMapper.class);
	User userl = userMapper.selectOne(user);
	System.out.println(userl);
}

在sqlSession中添加⽅法:

public interface SqlSession {

    public <T> T getMappper(Class<?> mapperClass);

}

对应实现类:

@Override
    public <T> T getMapper(Class<?> mapperClass) {
        // 使用JDK动态代理来为Dao接口生成代理对象,并返回

        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne
                // 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名
                // 方法名:findAll
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();

                String statementId = className+"."+methodName;

                // 准备参数2:params:args
                // 获取被调用方法的返回值类型
                Type genericReturnType = method.getGenericReturnType();
                // 判断是否进行了 泛型类型参数化
                if(genericReturnType instanceof ParameterizedType){
                    List<Object> objects = selectList(statementId, args);
                    return objects;
                }

                return selectOne(statementId,args);

            }
        });

        return (T) proxyInstance;
    }

1.6、 修改删除补充

1.6.1、编码步骤

  1. 修改XMLMapperBuilder的parse方法:解析<update><delete>标签
  2. 在SqlSession及DefalutSqlSession添加update及delete方法
  3. 在getMapper方法中对当前执行的sql语句进行判断,决定调用增删改查的那个方法
  4. 在Executor及simpleExecutor中添加update及delete方法

1.6.2、代码示例

XMLMapperBuilder:

public class XMLMapperBuilder {

    private Configuration configuration;

    public XMLMapperBuilder(Configuration configuration) {
        this.configuration =configuration;
    }

    public void parse(InputStream inputStream) throws DocumentException {

        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();

        String namespace = rootElement.attributeValue("namespace");

        List<Element> selectlist = rootElement.selectNodes("//select");
        Elementforeach(selectlist,namespace);

        List<Element> updatelist = rootElement.selectNodes("//update");
        Elementforeach(updatelist,namespace);

        List<Element> deletelist = rootElement.selectNodes("//delete");
        Elementforeach(deletelist,namespace);

    }

    public void Elementforeach(List<Element> list,String namespace){
        for (Element element : list) {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String paramterType = element.attributeValue("paramterType");
            String sqlText = element.getTextTrim();
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setResultType(resultType);
            mappedStatement.setParamterType(paramterType);
            mappedStatement.setSql(sqlText);
            String key = namespace+"."+id;
            configuration.getMappedStatementMap().put(key,mappedStatement);
        }
    }
}

SqlSession:

//根据条件进行修改
public Integer update(String statementid,Object... params) throws Exception;
    
//根据条件进行删除
public Integer delete(String statementid,Object... params) throws Exception;

DefaultSqlSession:

public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <E> List<E> selectList(String statementid, Object... params) throws Exception {

        //将要去完成对simpleExecutor里的query方法的调用
        simpleExecutor simpleExecutor = new simpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);

        return (List<E>) list;
    }

    @Override
    public <T> T selectOne(String statementid, Object... params) throws Exception {
        List<Object> objects = selectList(statementid, params);
        if(objects.size()==1){
            return (T) objects.get(0);
        }else {
            throw new RuntimeException("查询结果为空或者返回结果过多");
        }
    }

    @Override
    public Integer update(String statementid, Object... params) throws Exception {
        Executor simpleExecutor = new simpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
        Integer i = simpleExecutor.update(configuration, mappedStatement, params);
        return i;
    }
    @Override
    public Integer delete(String statementid, Object... params) throws Exception {
        Executor simpleExecutor = new simpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
        Integer i = simpleExecutor.delete(configuration, mappedStatement, params);
        return i;
    }
    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        // 使用JDK动态代理来为Dao接口生成代理对象,并返回
        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne
                // 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名
                // 方法名:findAll
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();

                String statementId = className+"."+methodName;

                // 准备参数2:params:args
                // 获取被调用方法的返回值类型
              Type genericReturnType = method.getGenericReturnType();
                // 判断是否进行了 泛型类型参数化
                if(genericReturnType instanceof ParameterizedType){
                    List<Object> objects = selectList(statementId, args);
                    return objects;
                }
                MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
                String sql = mappedStatement.getSql();
                if(sql.startsWith("update")){
                    return update(statementId,args);
                }
                if(sql.startsWith("delete")){
                    return delete(statementId,args);
                }
                return selectOne(statementId,args);
            }
        });
        return (T) proxyInstance;
    }
}

SimpleExecutor:

public class simpleExecutor implements  Executor {


    @Override                                                                                //user
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        // 1. 注册驱动,获取连接
        Connection connection = configuration.getDataSource().getConnection();

        // 2. 获取sql语句 : select * from user where id = #{id} and username = #{username}
            //转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);

        // 3.获取预处理对象:preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        // 4. 设置参数
            //获取到了参数的全路径
         String paramterType = mappedStatement.getParamterType();
         Class<?> paramtertypeClass = getClassType(paramterType);

        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();

            //反射
            Field declaredField = paramtertypeClass.getDeclaredField(content);
            //暴力访问
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);
            preparedStatement.setObject(i+1,o);

        }
        // 5. 执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);

        ArrayList<Object> objects = new ArrayList<>();

        // 6. 封装返回结果集
        while (resultSet.next()){
            Object o =resultTypeClass.newInstance();
            //元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {

                // 字段名
                String columnName = metaData.getColumnName(i);
                // 字段的值
                Object value = resultSet.getObject(columnName);

                //使用反射或者内省,根据数据库表和实体的对应关系,完成封装
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o,value);


            }
            objects.add(o);

        }
            return (List<E>) objects;

    }
    @Override
    public Integer update(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception {
        // 1. 注册驱动,获取连接
        Connection connection = configuration.getDataSource().getConnection();

        // 2. 获取sql语句 : select * from user where id = #{id} and username = #{username}
        //转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);

        // 3.获取预处理对象:preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        // 4. 设置参数
        //获取到了参数的全路径
        String paramterType = mappedStatement.getParamterType();
        Class<?> paramtertypeClass = getClassType(paramterType);

        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();

            //反射
            Field declaredField = paramtertypeClass.getDeclaredField(content);
            //暴力访问
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);
            preparedStatement.setObject(i+1,o);
        }
        // 5. 执行sql
        int i = preparedStatement.executeUpdate();
        return i;
    }
    @Override
    public Integer delete(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception {
        // 1. 注册驱动,获取连接
        Connection connection = configuration.getDataSource().getConnection();

        // 2. 获取sql语句 : select * from user where id = #{id} and username = #{username}
        //转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);

        // 3.获取预处理对象:preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        // 4. 设置参数
        //获取到了参数的全路径
        String paramterType = mappedStatement.getParamterType();
        Class<?> paramtertypeClass = getClassType(paramterType);
        if(paramtertypeClass == Integer.class){
            preparedStatement.setObject(1, params[0]);
        }else {
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();

            //反射
            Field declaredField = paramtertypeClass.getDeclaredField(content);
            //暴力访问
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);

            preparedStatement.setObject(i + 1, o);
        }
        }

        // 5. 执行sql
        int i = preparedStatement.executeUpdate();
        return i;

    }

    private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
        if(paramterType!=null){
            Class<?> aClass = Class.forName(paramterType);
            return aClass;
        }
         return null;

    }
    /**
     * 完成对#{}的解析工作:1.将#{}使用?进行代替,2.解析出#{}里面的值进行存储
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        //标记处理类:配置标记解析器来完成对占位符的解析处理工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        //解析出来的sql
        String parseSql = genericTokenParser.parse(sql);
        //#{}里面解析出来的参数名称
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

        BoundSql boundSql = new BoundSql(parseSql,parameterMappings);
         return boundSql;

    }
}

2、Mybatis 相关概念

2.1、对象/关系数据库映射(ORM)

        ORM全称Object/Relation Mapping:表示对象-关系映射的缩写。

        ORM完成⾯向对象的编程语⾔到关系数据库的映射。当ORM框架完成映射后,程序员既可以利⽤⾯向对象程序设计语⾔的简单易⽤性,⼜可以利⽤关系数据库的技术优势。ORM把关系数据库包装成⾯向对象的模型。ORM框架是⾯向对象设计语⾔与关系数据库发展不同步时的中间解决⽅案。采⽤ORM框架后,应⽤程序不再直接访问底层数据库,⽽是以⾯向对象的⽅式来操作持久化对象,⽽ORM框架则将这些⾯向对象的操作转换成底层SQL操作。ORM框架实现的效果:把对持久化对象的保存、修改、删除等操作,转换为对数据库的操作。

2.2、Mybatis历史与简介

        MyBatis是⼀款优秀的基于ORM的半⾃动轻量级持久层框架,它⽀持定制化SQL、存储过程以及⾼级映射。MyBatis避免了⼏乎所有的JDBC代码和⼿动设置参数以及获取结果集。MyBatis可以使⽤简单的XML或注解来配置和映射原⽣类型、接⼝和Java的POJO (Plain Old Java Objects,普通⽼式Java对象)为数据库中的记录。

        原是apache的⼀个开源项⽬iBatis, 2010年6⽉这个项⽬由apache software foundation 迁移到了google code,随着开发团队转投Google Code旗下,ibatis3.x正式更名为Mybatis ,代码于2013年11⽉迁移到Github。

        iBATIS⼀词来源于“internet”和“abatis”的组合,是⼀个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)

2.3、Mybatis的优势

        Mybatis是⼀个半⾃动化的持久层框架,对开发⼈员开说,核⼼sql还是需要⾃⼰进⾏优化,sql和java编码进⾏分离,功能边界清晰,⼀个专注业务,⼀个专注数据。

3、Mybatis基本应用

3.1、快速入门

3.1.1、官网

        MyBatis官⽹地址:mybatis – MyBatis 3 | 简介

3.1.2、开发步骤

  • ①添加MyBatis的坐标
  • ②创建player数据表
  • ③编写Player实体类
  • ④编写映射⽂件PlayerMapper.xml
  • ⑤编写核⼼⽂件SqlMapConfig.xml
  • ⑥编写测试类

3.1.3、环境搭建

1、导⼊MyBatis的坐标和其他相关坐标
<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.blnp.net</groupId>
    <artifactId>mybatis-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <!--引入依赖-->
    <dependencies>
        <!--mybatis坐标-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <!--mysql驱动坐标-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
            <scope>runtime</scope>
        </dependency>
        <!--单元测试坐标-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--⽇志坐标-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
    </dependencies>
</project>
2、创建 player 数据表

DROP TABLE IF EXISTS `player`;
CREATE TABLE `player`  (
  `player_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '球员ID',
  `team_id` int(11) NOT NULL COMMENT '球队ID',
  `player_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '球员姓名',
  `height` float(3, 2) NULL DEFAULT NULL COMMENT '球员身高',
  PRIMARY KEY (`player_id`) USING BTREE,
  UNIQUE INDEX `player_name`(`player_name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10038 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of player
-- ----------------------------
INSERT INTO `player` VALUES (10001, 1001, '韦恩-艾灵顿', 1.93);
INSERT INTO `player` VALUES (10002, 1001, '雷吉-杰克逊', 1.91);
INSERT INTO `player` VALUES (10003, 1001, '安德烈-德拉蒙德', 2.11);
INSERT INTO `player` VALUES (10004, 1001, '索恩-马克', 2.16);
INSERT INTO `player` VALUES (10005, 1001, '布鲁斯-布朗', 1.96);
INSERT INTO `player` VALUES (10006, 1001, '兰斯顿-加洛韦', 1.88);
INSERT INTO `player` VALUES (10007, 1001, '格伦-罗宾逊三世', 1.98);
INSERT INTO `player` VALUES (10008, 1001, '伊斯梅尔-史密斯', 1.83);
INSERT INTO `player` VALUES (10009, 1001, '扎扎-帕楚里亚', 2.11);
INSERT INTO `player` VALUES (10010, 1001, '乔恩-洛伊尔', 2.08);
INSERT INTO `player` VALUES (10011, 1001, '布雷克-格里芬', 2.08);
INSERT INTO `player` VALUES (10012, 1001, '雷吉-巴洛克', 2.01);
INSERT INTO `player` VALUES (10013, 1001, '卢克-肯纳德', 1.96);
INSERT INTO `player` VALUES (10014, 1001, '斯坦利-约翰逊', 2.01);
INSERT INTO `player` VALUES (10015, 1001, '亨利-埃伦森', 2.11);
INSERT INTO `player` VALUES (10016, 1001, '凯里-托马斯', 1.91);
INSERT INTO `player` VALUES (10017, 1001, '何塞-卡尔德隆', 1.91);
INSERT INTO `player` VALUES (10018, 1001, '斯维亚托斯拉夫-米凯卢克', 2.03);
INSERT INTO `player` VALUES (10019, 1001, '扎克-洛夫顿', 1.93);
INSERT INTO `player` VALUES (10020, 1001, '卡林-卢卡斯', 1.85);
INSERT INTO `player` VALUES (10021, 1002, '维克多-奥拉迪波', 1.93);
INSERT INTO `player` VALUES (10022, 1002, '博扬-博格达诺维奇', 2.03);
INSERT INTO `player` VALUES (10023, 1002, '多曼塔斯-萨博尼斯', 2.11);
INSERT INTO `player` VALUES (10024, 1002, '迈尔斯-特纳', 2.11);
INSERT INTO `player` VALUES (10025, 1002, '赛迪斯-杨', 2.03);
INSERT INTO `player` VALUES (10026, 1002, '达伦-科里森', 1.83);
INSERT INTO `player` VALUES (10027, 1002, '韦斯利-马修斯', 1.96);
INSERT INTO `player` VALUES (10028, 1002, '泰瑞克-埃文斯', 1.98);
INSERT INTO `player` VALUES (10029, 1002, '道格-迈克德莫特', 2.03);
INSERT INTO `player` VALUES (10030, 1002, '科里-约瑟夫', 1.91);
INSERT INTO `player` VALUES (10031, 1002, '阿龙-霍勒迪', 1.85);
INSERT INTO `player` VALUES (10032, 1002, 'TJ-利夫', 2.08);
INSERT INTO `player` VALUES (10033, 1002, '凯尔-奥奎因', 2.08);
INSERT INTO `player` VALUES (10034, 1002, '埃德蒙-萨姆纳', 1.96);
INSERT INTO `player` VALUES (10035, 1002, '达文-里德', 1.98);
INSERT INTO `player` VALUES (10036, 1002, '阿利兹-约翰逊', 2.06);
INSERT INTO `player` VALUES (10037, 1002, '伊凯·阿尼博古', 2.08);

SET FOREIGN_KEY_CHECKS = 1;
3、编写 PlayEntity 实体
package com.blnp.net.entity;

import java.io.Serializable;

public class PlayerEntity implements Serializable {
    /**
    * 球员ID
    */
    private Integer playerId;

    /**
    * 球队ID
    */
    private Integer teamId;

    /**
    * 球员姓名
    */
    private String playerName;

    /**
    * 球员身高
    */
    private Double height;

    private static final long serialVersionUID = 1L;

    public Integer getPlayerId() {
        return playerId;
    }

    public void setPlayerId(Integer playerId) {
        this.playerId = playerId;
    }

    public Integer getTeamId() {
        return teamId;
    }

    public void setTeamId(Integer teamId) {
        this.teamId = teamId;
    }

    public String getPlayerName() {
        return playerName;
    }

    public void setPlayerName(String playerName) {
        this.playerName = playerName;
    }

    public Double getHeight() {
        return height;
    }

    public void setHeight(Double height) {
        this.height = height;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", playerId=").append(playerId);
        sb.append(", teamId=").append(teamId);
        sb.append(", playerName=").append(playerName);
        sb.append(", height=").append(height);
        sb.append("]");
        return sb.toString();
    }
}
4、编写 PlayerMapper 映射文件
<?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">
<mapper namespace="com.blnp.net.dao.PlayerEntityMapper">

  <!--实体类字段映射关系-->
  <resultMap id="BaseResultMap" type="com.blnp.net.entity.PlayerEntity">
    <id column="player_id" jdbcType="INTEGER" property="playerId" />
    <result column="team_id" jdbcType="INTEGER" property="teamId" />
    <result column="player_name" jdbcType="VARCHAR" property="playerName" />
    <result column="height" jdbcType="FLOAT" property="height" />
  </resultMap>

  <!--表通用字段-->
  <sql id="Base_Column_List">
    player_id, team_id, player_name, height
  </sql>

  <!--根据主键ID查询-->
  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List" />
    from player
    where player_id = #{playerId,jdbcType=INTEGER}
  </select>

  <!--根据主键ID删除-->
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
    delete from player
    where player_id = #{playerId,jdbcType=INTEGER}
  </delete>

  <!--单条全字段新增-->
  <insert id="insert" keyColumn="player_id" keyProperty="playerId" parameterType="com.blnp.net.entity.PlayerEntity" useGeneratedKeys="true">
    insert into player (team_id, player_name, height
      )
    values (#{teamId,jdbcType=INTEGER}, #{playerName,jdbcType=VARCHAR}, #{height,jdbcType=FLOAT}
      )
  </insert>

  <!--单条动态新增-->
  <insert id="insertSelective" keyColumn="player_id" keyProperty="playerId" parameterType="com.blnp.net.entity.PlayerEntity" useGeneratedKeys="true">
    insert into player
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="teamId != null">
        team_id,
      </if>
      <if test="playerName != null">
        player_name,
      </if>
      <if test="height != null">
        height,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="teamId != null">
        #{teamId,jdbcType=INTEGER},
      </if>
      <if test="playerName != null">
        #{playerName,jdbcType=VARCHAR},
      </if>
      <if test="height != null">
        #{height,jdbcType=FLOAT},
      </if>
    </trim>
  </insert>

  <!--根据主键动态更新-->
  <update id="updateByPrimaryKeySelective" parameterType="com.blnp.net.entity.PlayerEntity">
    update player
    <set>
      <if test="teamId != null">
        team_id = #{teamId,jdbcType=INTEGER},
      </if>
      <if test="playerName != null">
        player_name = #{playerName,jdbcType=VARCHAR},
      </if>
      <if test="height != null">
        height = #{height,jdbcType=FLOAT},
      </if>
    </set>
    where player_id = #{playerId,jdbcType=INTEGER}
  </update>

  <!--根据主键全字段更新-->
  <update id="updateByPrimaryKey" parameterType="com.blnp.net.entity.PlayerEntity">
    update player
    set team_id = #{teamId,jdbcType=INTEGER},
      player_name = #{playerName,jdbcType=VARCHAR},
      height = #{height,jdbcType=FLOAT}
    where player_id = #{playerId,jdbcType=INTEGER}
  </update>
</mapper>
5、编写MyBatis 核心文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <!--加载外部的properties文件-->
    <properties resource="jdbc.properties"></properties>

    <!--给实体类的全限定类名给别名-->
    <typeAliases>
        <!--给单独的实体起别名-->
        <!--  <typeAlias type="com.blnp.net.entity.PlayerEntity" alias="playerEntity"></typeAlias>-->

        <!--批量起别名:该包下所有的类的本身的类名:别名还不区分大小写-->
        <package name="com.blnp.net.entity"/>
    </typeAliases>

    <!--environments:运行环境-->
    <environments default="development">
        <environment id="development">
            <!--当前事务交由JDBC进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--当前使用mybatis提供的连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--引入映射配置文件-->
    <mappers>
        <mapper resource="mapper/PlayerMapper.xml"></mapper>
    </mappers>
</configuration>
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3307/my_test
jdbc.username=root
jdbc.password=admin@123
6、编写测试代码
package com.blnp.net.demo;

import com.blnp.net.dao.PlayerEntityMapper;
import com.blnp.net.entity.PlayerEntity;
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.Test;

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

/**
 * <p></p>
 *
 * @author lyb 2045165565@qq.com
 * @createDate 2024/5/28 17:10
 */
public class QueryTest {

    public static void main(String[] args) throws IOException {
        //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        //2.解析了配置文件,并创建了sqlSessionFactory工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        //3.生产sqlSession
        // 默认开启一个事务,但是该事务不会自动提交
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //在进行增删改操作时,要手动提交事务
        //4.sqlSession调用方法:查询所有selectList  查询单个:selectOne 添加:insert  修改:update 删除:delete
        List<PlayerEntity> users = sqlSession.selectList("com.blnp.net.dao.PlayerEntityMapper.selectAll");
        for (PlayerEntity user : users) {
            System.out.println(user);
        }
        //释放资源
        sqlSession.close();
    }

    @Test
    public void test1() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //事务自动提交
        SqlSession sqlSession = sqlSessionFactory.openSession(true);

        PlayerEntity user = new PlayerEntity();
//        user.setPlayerId(10038);
        user.setTeamId(1003);
        user.setPlayerName("张飞");
        user.setHeight(1.89);
        sqlSession.insert("com.blnp.net.dao.PlayerEntityMapper.insert",user);

        sqlSession.close();
    }

    @Test
    public void test2() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        PlayerEntityMapper mapper = sqlSession.getMapper(PlayerEntityMapper.class);
        PlayerEntity playerEntity = mapper.selectByPrimaryKey(10038);
        System.out.println("playerEntity = " + playerEntity.toString());
    }
}

3.1.4、源码下载

  • 快速入门示例工程:mybatis-demo.7z - 蓝奏云

3.2、MyBatis的映射⽂件概述

3.3、核⼼配置⽂件分析

3.3.1、MyBatis核⼼配置⽂件层级关系

3.3.2、MyBatis常⽤配置解析

1、environments标签

        其中,事务管理器(transactionManager)类型有两种:

  • JDBC:这个配置就是直接使⽤了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作⽤域。
  • MANAGED:这个配置⼏乎没做什么。它从来不提交或回滚⼀个连接,⽽是让容器来管理事务的整个⽣命周期(⽐如 JEE 应⽤服务器的上下⽂)。 默认情况下它会关闭连接,然⽽⼀些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻⽌它默认的关闭⾏为。

        其中,数据源(dataSource)类型有三种:

  • UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
  • POOLED:这种数据源的实现利⽤“池”的概念将 JDBC 连接对象组织起来。
  • JNDI:这个数据源的实现是为了能在如 EJB 或应⽤服务器这类容器中使⽤,容器可以集中或在外部配置数据源,然后放置⼀个 JNDI 上下⽂的引⽤。
2、mapper标签

        该标签的作⽤是加载映射的,加载⽅式有如下⼏种:

<!-- 使⽤相对于类路径的资源引⽤,例如: -->
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>

<!-- 使⽤完全限定资源定位符(URL),例如: -->
<mapper url="file:///var/mappers/AuthorMapper.xml"/>

<!-- 使⽤映射器接⼝实现类的完全限定类名,例如: -->
<mapper class="org.mybatis.builder.AuthorMapper"/>

<!-- 将包内的映射器接⼝实现全部注册为映射器,例如: -->
<package name="org.mybatis.builder"/>
3、Properties标签

        实际开发中,习惯将数据源的配置信息单独抽取成⼀个properties⽂件,该标签可以加载额外配置的properties⽂件。

4、typeAliases标签

类型别名是为Java 类型设置⼀个短的名字。原来的类型名称配置如下:

配置 typeAliases,为 com.blnp.net.domain.User 定义别名为user:

        上⾯我们是⾃定义的别名,mybatis框架已经为我们设置好的⼀些常⽤的类型的别名:

5、其它标签

        详见官网说明:mybatis – MyBatis 3 | 配置

3.4、Mybatis相应API介绍

3.4.1、SqlSession⼯⼚构建器SqlSessionFactoryBuilder

        常⽤API:SqlSessionFactory build(InputStream inputStream),通过加载mybatis的核⼼⽂件的输⼊流的形式构建⼀个SqlSessionFactory对象。

String resource = "org/mybatis/builder/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);

        其中, Resources ⼯具类,这个类在 org.apache.ibatis.io 包中。Resources 类帮助你从类路径下、⽂件系统或⼀个 web URL 中加载资源⽂件。

3.4.2、SqlSession⼯⼚对象SqlSessionFactory

        SqlSessionFactory 有多个个⽅法创建SqlSession 实例。常⽤的有如下两个:

3.4.3、SqlSession会话对象

        SqlSession 实例在 MyBatis 中是⾮常强⼤的⼀个类。在这⾥你会看到所有执⾏语句、提交或回滚事务和获取映射器实例的⽅法。执⾏语句的⽅法主要有:

<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)

//操作事务的⽅法主要有:
void commit()
void rollback()

3.5、Mybatis的Dao层实现

3.5.1、传统开发⽅式

1、编写UserDao接⼝
public interface UserDao {
    List<User> findAll() throws IOException;
}
2、编写UserDaoImpl实现
public class UserDaoImpl implements UserDao {
    
    public List<User> findAll() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<User> userList = sqlSession.selectList("userMapper.findAll");
        sqlSession.close();
        return userList;
    }
}
3、测试传统⽅式
@Test
public void testTraditionDao() throws IOException {
    UserDao userDao = new UserDaoImpl();
    List<User> all = userDao.findAll();
    System.out.println(all);
}

3.5.2、代理开发⽅式

1、代理开发⽅式介绍

        采⽤ Mybatis 的代理开发⽅式实现 DAO 层的开发,这种⽅式是我们后⾯的主流。Mapper 接口开发⽅法只需要程序员编写Mapper 接⼝(相当于Dao 接⼝),由Mybatis 框架根据接口定义创建接⼝的动态代理对象,代理对象的⽅法体同上边Dao接⼝实现类⽅法。

        Mapper 接⼝开发需要遵循以下规范:

1) Mapper.xml⽂件中的namespace与mapper接⼝的全限定名相同

2) Mapper接⼝⽅法名和Mapper.xml中定义的每个statement的id相同

3) Mapper接⼝⽅法的输⼊参数类型和mapper.xml中定义的每个sql的parameterType的类型相同

4) Mapper接⼝⽅法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同

2、编写UserMapper接⼝

3、测试代理⽅式
@Test
public void testProxyDao() throws IOException {
    InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    SqlSession sqlSession = sqlSessionFactory.openSession();
    //获得MyBatis框架⽣成的UserMapper接⼝的实现类
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.findById(1);
    System.out.println(user);
    sqlSession.close();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/673933.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大模型+RAG,全面介绍!

1 介绍 大型语言模型&#xff08;LLMs&#xff09;在处理特定领域或高度专业化的查询时存在局限性**&#xff0c;如生成不正确信息或“幻觉”。**缓解这些限制的一种有前途的方法是检索增强生成&#xff08;RAG&#xff09;&#xff0c;RAG就像是一个外挂&#xff0c;将外部数…

环路检测00

题目链接 环路检测 题目描述 注意点 返回环路的开头节点 解答思路 快慢指针确认链表中是否有环&#xff0c;如果无环则快指针最终会到达链表尾部&#xff0c;如果有环则快慢指针会相遇&#xff0c;当快慢指针相遇&#xff0c;此时新的节点node从head开始出发&#xff0c;与…

探索数据结构:便捷的双向链表

&#x1f511;&#x1f511;博客主页&#xff1a;阿客不是客 &#x1f353;&#x1f353;系列专栏&#xff1a;渐入佳境之数据结构与算法 欢迎来到泊舟小课堂 &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 ​​ 前言 前面我们学习了单链表&#xff0c;它解…

微服务中feign远程调用相关的各种超时问题

1. 引言 在spring cloud微服中&#xff0c;feign远程调用可能是大家每天都接触到东西&#xff0c;但很多同学却没咋搞清楚这里边的各种超时问题&#xff0c;生产环境可能会蹦出各种奇怪的问题。 首先说下结论&#xff1a; 1)只使用feign组件&#xff0c;不使用ribbion组件&…

FuTalk设计周刊-Vol.045

#AI漫谈 热点捕手 1、新模型 Stable Diffusion 3 与 Stable Cascade 全面解析 最近 Stability AI 又接连推出了 2 个新的模型&#xff1a;Stable Diffusion 3 和 Stable Cascade&#xff0c;在图像生成效率和质量上比半年前推出的 SDXL 1.0 有了明显提升&#xff0c;今天就为…

PyQt5学习系列之ui转py报错问题

PyQt5学习系列之ui转py报错问题 前言解决步骤一步骤二步骤三 总结 前言 设计好的界面出现ui转py问题&#xff0c;一直出现报错。 一开始通过pycharm进行转化&#xff0c;采用tool工具&#xff0c;一直报错。后用vscode进行转化&#xff0c;转化成功。最终发现问题是语句问题。…

关于恶意软件、行为透明度和移动恶意软件的最新审核变动

关于隐私权、欺骗行为和设备滥用这块,谷歌官方政策一直在改动,最新政策更新出来了,2024 年 8 月 31 日才会进行实施,希望在这期间,开发者们能早点修改重新提包,避免过期后被扫荡下架。 目录 变动新增关于 - 敏感信息访问权限和 API修改范围拒审解决变动新增 关于 - 敏感…

Linux 僵尸进程和孤儿进程

一.Z(zombie)-僵尸进程 1.僵死状态&#xff08;Zombies&#xff09;是一个比较特殊的状态。当进程退出并且父进程&#xff08;使用wait()系统调用后&#xff09;没有读取到子进程退出的返回代码时就会产生僵死(尸)进程 2.僵死进程会以终止状态保持在进程表中&#xff0c;并且会…

二进制文件(.bin等文件)转C语言数组形式hex文件(.c等文件)

使用python脚本把二进制文件&#xff08;.bin等文件&#xff09;转C语言数组形式hex文件&#xff08;.c等文件&#xff09;&#xff0c;具体代码如下&#xff1a; &#xff08;1&#xff09;读取.bin文件数据 &#xff08;2&#xff09;生成C语言数组定义 &#xff08;3&…

中国高分辨率土壤质地数据(1KM)

土壤中各粒级占土壤重量的百分比组合&#xff0c;叫做土壤质地。土壤质地是土壤的最基本物理性质之一&#xff0c;对土壤的各种性状&#xff0c;如土壤的通透性、保蓄性、耕性以及养分含量等都有很大的影响是评价土壤肥力和作物适宜性的重要依据。 中国土壤质地空间分布数据是根…

基于全志T507-H的Igh EtherCAT主站案例分享

基于全志T507-H的Linux-RT IgH EtherCAT主站演示 下文主要介绍基于全志T507-H&#xff08;硬件平台&#xff1a;创龙科技TLT507-EVM评估板&#xff09;案例&#xff0c;按照创龙科技提供的案例用户手册进行操作得出测试结果。 本次演示的开发环境&#xff1a; Windows开发环…

岩土工程监测中振弦采集仪数据处理与解读的挑战与方法

岩土工程监测中振弦采集仪数据处理与解读的挑战与方法 岩土工程监测是确保工程安全的重要环节&#xff0c;而振弦采集仪是岩土工程监测中常用的一种设备。振弦采集仪通过测量土体的振动响应&#xff0c;可以获取土体的力学性质和结构特征&#xff0c;为工程设计和施工提供重要…

【MySQL】MySQL Connect -- 详解

一、Connector / C 使用 要使用 C 语言连接 MySQL&#xff0c;需要使用 MySQL 官网提供的库&#xff0c;可以去官网进行下载&#xff1a;MySQL :: MySQL Community Downloads 我们使用 C 接口库来进行连接&#xff0c;要正确使用&#xff0c;还需要做一些准备工作&#xff1a…

计算机视觉与模式识别实验1-4 图像的傅立叶变换

文章目录 &#x1f9e1;&#x1f9e1;实验流程&#x1f9e1;&#x1f9e1;1. 傅立叶变换1.a 绘制一个二值图像矩阵,并将其傅立叶函数可视化。1.b 利用傅立叶变换分析两幅图像的相关性&#xff0c;定位图像特征。读入图像‘text.png&#xff0c;抽取其中的字母‘a’ 2. 离散余弦…

操作字符串获取文件名字(包含类型)

记录一种操作字符串获取文件名字的操作方式&#xff0c;方便后期的使用。示例&#xff1a; 输入&#xff1a;"D:/code/Test/Test.txt" 输出&#xff1a;"Test.txt" 设计思路&#xff1a; 首先查找路径中最后一个”/“&#xff0c;然后再通过字符串截取的…

Flutter开发效率提升1000%,Flutter Quick教程之对写好的Widget进行嵌套

通常写代码的时候&#xff0c;我们是先写好外面的Widget&#xff0c;再写里面的Widget。但是&#xff0c;也有的时候&#xff0c;我们写好了一个Widget&#xff0c;但是我们觉得有必要再在外面嵌套一个Widget&#xff0c;这时候应该怎么做呢&#xff1f;&#xff08;还有其他方…

NVIDIA发布重磅AI创新,黄仁勋在COMPUTEX大会预示计算未来

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

coredns 被误删了,可以通过重新应用 coredns 的 Deployment 或 DaemonSet 配置文件来恢复

如果 coredns 被误删了&#xff0c;可以通过重新应用 coredns 的 Deployment 或 DaemonSet 配置文件来恢复。以下是恢复 coredns 的步骤&#xff1a; 1. 下载 coredns 配置文件 你可以从 Kubernetes 的官方 GitHub 仓库下载 coredns 的配置文件。以下是下载并应用配置文件的步…

Android开机动画,framework修改Bootanimation绘制文字。

文章目录 Android开机动画&#xff0c;framework修改Bootanimation动画绘制文字。 Android开机动画&#xff0c;framework修改Bootanimation动画绘制文字。 frameworks/base/cmds/bootanimation/bootanimation.cpp 绘制时间的一个方法 // We render 12 or 24 hour time. void…

使用Landsat的NDVI和NDWI阈值法土地分类

目录 分类效果源代码分类效果 创建一个包含多个层的影像合成:水体(NDWI > 0.5),植被(NDVI > 0.2),阴影区域的裸地(bare2但不包括bare1),和其他裸地(bare1)。然后,使用mosaic()方法合并这些层,并用clip(hh)方法裁剪到研究区域的范围。 源代码 var hh = ee.…