热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

mybaits源码分析(四)mybaits数据源详解

mybaits源码分析(四)数据源前言:对于ORM框架,数据源都是不可或少的组件,虽然mybaits框架整合spring时,可以由外部指定数据源对象,构建到SqlSessionFa

mybaits源码分析(四) 数据源

前言:对于ORM框架,数据源都是不可或少的组件,虽然mybaits框架整合spring时,可以由外部指定数据源对象,构建到SqlSessionFactoryBean中,但是对于mybaits自身对数据源的实现原理还是值得探究的,本文将通过对MyBatis框架的数据源 结构进行详尽的分析,并且深入解析MyBatis的连接池。



本文内容初步分为下面几个部分
        

    mybaits数据源的概述
        
    mybaits数据源的构建和使用
                
    UnpooledDataSource的剖析
        
    PooledDataSource的剖析
        
    简易数据源案例演示


 一、Mybaits数据源的概述

1、数据源分类
       MyBatis把数据源DataSource分为三种
        UnpooledDataSource :不使用连接池的数据源
        PooledDataSource :使用连接池的数据源
        JndiDataSource:使用JNDI实现的数据源

2、数据源类结构

数据源由DatasourceFacotry创建,UnpooledDataSourceFacotry创建UnpooledDataSource ,PooledDataSourceFacotry创建PooledDataSource ,并且UnpooledDataSourceFacotry继承UnpooledDataSourceFacotry用于通用设置属性用途,PooledDataSource 内部维护了UnpooledDataSource 的实例,用于真实创建数据库链接, PooledDataSource 内部连接池缓存的是用于增强并包装真实连接对象和代理连接对象的PooledConnection

类结构如下:

 3、数据源的创建

    我们主要讲解UnpooledDataSource和PooledDataSource,其中UnpooledDataSource是没有使用连接池的数据源,PooledDataSource包装了UnpooledDataSource,并且扩展了连接池功能,用UnpooledDataSource依据创建连接,内部用列表容器实现连接池功能。
    数据源的创建,采用了工厂模式,UnpooledDataSource和PooledDataSource的创建,分别交于不同的实现了DataSourceFactory的工厂类创建,并且设置了一些配置属性。下面是UnpooledDataSourceFactory创建数据源的过程。

public UnpooledDataSourceFactory() { // 构造函数创建对应工厂的数据源
this.dataSource = new UnpooledDataSource();
}
public void setProperties(Properties properties) { // 设置属性
Properties driverProperties = new Properties();
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
for (Object key : properties.keySet()) {
String propertyName = (String) key;
if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) { // 设置驱动属性
String value = properties.getProperty(propertyName);
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
} else if (metaDataSource.hasSetter(propertyName)) { // 其他常规数据,依据有无set方法进行设置
String value = (String) properties.get(propertyName);
Object cOnvertedValue= convertValue(metaDataSource, propertyName, value);
metaDataSource.setValue(propertyName, convertedValue);
} else {
throw new DataSourceException("Unknown DataSource property: " + propertyName);
}
}
if (driverProperties.size() > 0) {
metaDataSource.setValue("driverProperties", driverProperties);
}
}

  二、mybaits数据源的构建

 上面对数据源的分类,类图结构,以及工厂类创建数据源的过程进行了简单的介绍,知道框架会根据不同的工厂类创建不同的数据源,那么在mybaits中如何创建数据源工厂类的呢

 1、数据源的构建

  下面是xml配置文件

         
                
                
                
                
            

      数据源创建的时候,会根据dataSource节点的type属性,实例化对应的工厂类,进行数据源的创建,并且把子节点的props属性对添加到数据源的set方法中,POOLED表示会创建PooledDataSource数据源,Unpooled表示会创建UnpooledDataSource数据源。

        private DataSourceFactory dataSourceElement(XNode context) throws Exception {
          if (context != null) { // context是解析dataSource节点的内容
              String type = context.getStringAttribute("type"); // type属性
              Properties props = context.getChildrenAsProperties(); // 子属性键值对
              DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
              factory.setProperties(props);
              return factory;
          }
          throw new BuilderException("Environment declaration requires a DataSourceFactory.");
        }

    2、数据源的使用

    数据源的作用就是获取连接对象connection,那么在执行过程中,我们是哪个时候从数据源中获取连接对象的呢?从上篇事务我们了解到,连接是包装到Transaction对象中,当我们创建了SQLSession之后,通过SqlSession执行sql语句的时候,Mybatis才会调用DataSource数据源去创建Connection对象。

三、UnpooledDataSource详解

    UnpooledDataSource数据源是由UnpooledDataSourceFactory创建的,这个数据源就是直接实例化connection对象,然后供调用方使用,即使用UnpooledDataSource数据源的getConnection方法,每一次调用都会产生一个Connection对象。

public UnpooledDataSource(String driver, String url, String username, String password) {
this.driver = driver;
this.url = url;
this.username = username;
this.password = password;
}
public Connection getConnection() throws SQLException {
return doGetConnection(username, password);
}
private Connection doGetConnection(String username, String password) throws SQLException {
Properties props = new Properties();
if (driverProperties != null) {
props.putAll(driverProperties); // 其他参数
}
if (username != null) { // username参数
props.setProperty("user", username);
}
if (password != null) { // password参数
props.setProperty("password", password);
}
return doGetConnection(props);
}
private Connection doGetConnection(Properties properties) throws SQLException {
initializeDriver(); // 初始化驱动
Connection cOnnection= DriverManager.getConnection(url, properties); // 获得连接
configureConnection(connection);
return connection;
}

从上面我们知道,    UnpooledDataSource的getConnection就是直接创建jdbc连接Connection对象的过程。

四、PooledDataSource详解

    PooledDataSource数据源在上面我就介绍了它拥有连接池的数据源。现在我们看一下PooledDataSource的基本原理:PooledDataSource数据源将Connection对象包裹成了PooledConnection对象放到PoolState容器中进行维护。MyBatis将连接池中的PooledConnection分为两种状态: 空闲状态(idle)和活动状态(active),这两种状态的PooledConnection对象分别被存储到PoolState容器内的idleConnections和activeConnections两个List集合中。
    而PooledDataSource内部包装UnpooledDataSource的主要作用就是,在自身需要创建数据源的时候,可以调用UnpooledDataSource进行创建数据源。

1、PoolState和PooledDataSource的主要属性

public class PoolState {
protected PooledDataSource dataSource;
// 空闲连接
protected final List idleCOnnections= new ArrayList();
// 活跃连接
protected final List activeCOnnections= new ArrayList();

public class PooledDataSource implements DataSource {
private final PoolState state = new PoolState(this); // 封装空闲连接和活跃连接的集合
private final UnpooledDataSource dataSource; // unpooledDataSource用于真实获取连接用途
protected int poolMaximumActiveCOnnections= 10; // 最大活跃个数
protected int poolMaximumIdleCOnnections= 5; // 最大空闲个数
protected int poolMaximumCheckoutTime = 20000; // 最大工作超时时间
protected int poolTimeToWait = 20000; // 未获得连接的等待时间
protected String poolPingQuery = "NO PING QUERY SET"; // 心跳查询
protected boolean poolPingEnabled = false;
protected int poolPingCOnnectionsNotUsedFor= 0;
private int expectedConnectionTypeCode;
public PooledDataSource() {
dataSource = new UnpooledDataSource();
}

    2、核心执行逻辑

   类的属性结构讲完,连接池的工作流程也就清晰了,既然是连接池,我们从连接池的更新角度看,就存在打开连接和用完放回连接池的过程,这个过程分别对应popConnection和pushConnection方法。

         1) 取出连接popConnection

取出连接的过程,基本上就是如果idle有,就从idle取,否则,判断active的尺寸是否大于最大acitve尺寸,如果小于,那么就可以创建一个新的链接,如果大于等于,那就不能创建了,就取出最先使用的链接,判断是否过期,如果过期就rollback,更新成一个链接,如果没有过期,就等待,然后继续执行获取。

private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection cOnn= null; // 连接包装
long t = System.currentTimeMillis();
int localBadCOnnectionCount= 0;
while (cOnn== null) {
synchronized (state) { // 锁为容器对象
// 如果空闲不为空。
if (!state.idleConnections.isEmpty()) {
cOnn= state.idleConnections.remove(0); // 从空闲中取
} else {
// 如果活跃尺寸少于最大活跃
if (state.activeConnections.size() 创建一个新的--这个datasource 是UnpooledDataSource,即获取真实连接。
cOnn= new PooledConnection(dataSource.getConnection(), this);
} else {
// 否则从旧的中取
PooledConnection oldestActiveCOnnection= state.activeConnections.get(0);
long lOngestCheckoutTime= oldestActiveConnection.getCheckoutTime();
// 判断最先用的是否超时
if (longestCheckoutTime > poolMaximumCheckoutTime) {
// 是超时就移除旧的
state.activeConnections.remove(oldestActiveConnection);
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
try { // 并且旧的要回滚
oldestActiveConnection.getRealConnection().rollback();
} catch (SQLException e) {
}
}
// 把旧的真实连接取出,包装成一个新的PooledConnection
cOnn= new PooledConnection(oldestActiveConnection.getRealConnection(), this);
oldestActiveConnection.invalidate();
} else {
try {
// 如果没法获得连接就等待,等待恢复后,从新执行while下面的流程判断。
if (!countedWait) {
state.hadToWaitCount++;
countedWait = true;
}
if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
state.wait(poolTimeToWait); // 等待
} catch (InterruptedException e) {
break;
}
}
}
}
if (conn != null) {
if (conn.isValid()) {
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback(); // 再次保证全部进行了rollBack,才可以使用
}
state.activeConnections.add(conn); // 添加到activeConnections
} else {
cOnn= null;
}
}
}
}
return conn;
}

流程图如下图所示  

2) 归还连接oushConnection

从上面的分析我们知道,我们active集合,在没有超容时,会创建,那么我们的第一层判断idle容器的数量怎么来的呢?这个就要下面的归还连接的过程进行分析。
       归还连接的过程就比上面的简单多了,就是如果idel容器少于acitve最大限制,那么就是放到空闲容器中,如果超过了,就直接close(注意,这个是真实连接调用关闭连接)

protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
state.activeConnections.remove(conn); // 活跃池移除
if (conn.isValid()) {
// 如果空闲池少于最大空闲连接,就直接将活跃池中移除的添加到空闲池
if (state.idleConnections.size() state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
PooledConnection newCOnn= new PooledConnection(conn.getRealConnection(), this);
state.idleConnections.add(newConn);
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
conn.invalidate();
state.notifyAll();
// 否则直接关闭
} else {
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
conn.getRealConnection().close();
conn.invalidate();
}
} else {
// ....
}
}
}

 3、PooledConnection分析

上面我们分析的是对连接池直接操作的pushConnection和popConnection方法,那么我们外部是如何调用这2个方法的呢?首先popConnection方法很简单,就是被连接池接口方法getConnection调用的。而pushConnection方法呢?,难道我们每次使用完连接就要手动调用这个方法。
       肯定不能这样操作,我们希望的是,每次connection进行close的使用,实际上不进行close,而调用pushConnection方法,这种功能想都不要想,肯定是使用动态代理。具体实现是,我们PooledDataSource获取连接的是PooledConnection包装的ProxyConnection。

下面就是PooledConnection的主要属性和构造函数。

class PooledConnection implements InvocationHandler {

private static final String CLOSE = "close";
private static final Class[] IFACES = new Class[] { Connection.class };

private int hashCode = 0;
private PooledDataSource dataSource;
private Connection realConnection;
private Connection proxyConnection;
private long checkoutTimestamp;
private long createdTimestamp;
private long lastUsedTimestamp;
private boolean valid;

public PooledConnection(Connection connection, PooledDataSource dataSource) {
this.hashCode = connection.hashCode();
this.realCOnnection= connection;
this.dataSource = dataSource;
this.valid = true;
this.proxyCOnnection= (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
}

如上所示,PooledConnection是实现InvocationHandler类的代理增强,并且构造函数中,创建了一个代理连接对象。我们只要看这个类的invoke方法是如何增强的即可。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
dataSource.pushConnection(this); // 核心 -- 如果Colse方法就返回池中
return null;
} else {
try {
if (!Object.class.equals(method.getDeclaringClass())) {
checkConnection();
}
// 普通方法直接执行
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}

  五、简易数据源案例演示

1、MyPoolConnection 数据连接包装及代理增强和实例化

public class MyPoolConnection implements InvocationHandler {
private static final Class[] IFACES = new Class[] { Connection.class };
private MyPoolDataSource dataSource; // 保持数据源的链接
private Connection realConnection; // 真实连接
private Connection proxyConnection; // 代理连接
private long checkoutTimestamp; // 检测使用时间
public MyPoolConnection(Connection connection, MyPoolDataSource dataSource) {
this.realCOnnection= connection;
this.dataSource = dataSource;
this.proxyCOnnection= (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
}
public MyPoolDataSource getDataSource() {
return dataSource;
}
public void setDataSource(MyPoolDataSource dataSource) {
this.dataSource = dataSource;
}
public Connection getRealConnection() {
return realConnection;
}
public void setRealConnection(Connection realConnection) {
this.realCOnnection= realConnection;
}
public Connection getProxyConnection() {
return proxyConnection;
}
public void setProxyConnection(Connection proxyConnection) {
this.proxyCOnnection= proxyConnection;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ("close".equals(methodName)) {
dataSource.pushConnection(this);
} else {
if (!Object.class.equals(method.getDeclaringClass())) {
// 检测链接是否有效
}
return method.invoke(realConnection, args);
}
return null;
}
public long getCheckoutTime() {
return System.currentTimeMillis() - checkoutTimestamp;
}
public void setCheckoutTimestamp(long checkoutTimestamp) {
this.checkoutTimestamp = checkoutTimestamp;
}
}

2、MyPoolDataSource数据源

public class MyPoolDataSource extends AbstractDataSource {
// 数据源基本属性
private String driver;
private String url;
private String username;
private String password;
// 链接池属性
protected int poolMaximumActiveCOnnections= 10;
protected int poolMaximumIdleCOnnections= 5;
protected int poolMaximumCheckoutTime = 20000;
protected int poolTimeToWait = 20000;
// 连接容器
protected final List idleCOnnections= new ArrayList();
protected final List activeCOnnections= new ArrayList();

public MyPoolDataSource(String driver, String url, String username, String password) {
super();
this.driver = driver;
this.url = url;
this.username = username;
this.password = password;
}
@Override
public Connection getConnection() throws SQLException {
return popConnection(username, password).getProxyConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return popConnection(username, password).getProxyConnection();
}
/**
* 真实创建链接
*/
public Connection doGetConnection() throws SQLException {
initDriver();
Properties props = new Properties();
if (username != null) {
props.setProperty("user", username);
}
if (password != null) {
props.setProperty("password", password);
}
Connection cOnnection= DriverManager.getConnection(url, props);
return connection;
}
/**
* 用于判断是否已经加载驱动
*/
private static Map registeredDrivers = new ConcurrentHashMap();
/**
* 初始化数据源驱动
*/
private void initDriver() {
if (registeredDrivers.containsKey(this.driver)) {
try {
Class driverType = Class.forName(this.driver);
Driver driverInstance = (Driver) driverType.newInstance();
registeredDrivers.put(this.driver, driverInstance);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
}
/**
* 重点方法: 返回池中
*/
public void pushConnection(MyPoolConnection conn) throws SQLException {
synchronized (this) {
activeConnections.remove(conn);
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
// 空闲池有位置
if (idleConnections.size() MyPoolConnection newCOnn= new MyPoolConnection(conn.getRealConnection(), this);
idleConnections.add(newConn);
this.notifyAll(); // 通知有可能等待取连接的线程
} else {
// 直接真实释放
conn.getRealConnection().close();
}
}
}
/**
* 重点方法:从池中取连接
*/
public MyPoolConnection popConnection(String username, String password) throws SQLException {
MyPoolConnection cOnn= null;
while (cOnn== null) {
synchronized (this) {
// idel池有货
if (idleConnections.size() > 0) {
cOnn= idleConnections.remove(0);
} else {
// active池未满
if (activeConnections.size() Connection cOnnection= this.doGetConnection();
cOnn= new MyPoolConnection(connection, this);
activeConnections.add(conn);
} else {
MyPoolConnection oldCOnnection= this.activeConnections.get(0);
long lOngestCheckoutTime= oldConnection.getCheckoutTime();
// 旧的超时
if (longestCheckoutTime > poolMaximumCheckoutTime) {
Connection cOnnection= oldConnection.getRealConnection();
if (!connection.getAutoCommit()) {
connection.rollback();
}
cOnn= new MyPoolConnection(connection, this);
activeConnections.remove(oldConnection);
// 没取到
} else {
try {
wait(poolTimeToWait); // wait等待池等待poolTimeToWait时间,如果notifyAll就会提前取消等待
} catch (InterruptedException e) {
break;
}
}
}
}
}
}
// 取到连接了
if (conn != null) {
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
conn.setCheckoutTimestamp(System.currentTimeMillis());
}
// 没取到报错
if (cOnn== null) {
throw new SQLException(
"PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
return conn;
}
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getPoolMaximumActiveConnections() {
return poolMaximumActiveConnections;
}
public void setPoolMaximumActiveConnections(int poolMaximumActiveConnections) {
this.poolMaximumActiveCOnnections= poolMaximumActiveConnections;
}
public int getPoolMaximumIdleConnections() {
return poolMaximumIdleConnections;
}
public void setPoolMaximumIdleConnections(int poolMaximumIdleConnections) {
this.poolMaximumIdleCOnnections= poolMaximumIdleConnections;
}
public int getPoolMaximumCheckoutTime() {
return poolMaximumCheckoutTime;
}
public void setPoolMaximumCheckoutTime(int poolMaximumCheckoutTime) {
this.poolMaximumCheckoutTime = poolMaximumCheckoutTime;
}
public int getPoolTimeToWait() {
return poolTimeToWait;
}
public void setPoolTimeToWait(int poolTimeToWait) {
this.poolTimeToWait = poolTimeToWait;
}
public static Map getRegisteredDrivers() {
return registeredDrivers;
}
public static void setRegisteredDrivers(Map registeredDrivers) {
MyPoolDataSource.registeredDrivers = registeredDrivers;
}

public String logSate() {
return "空闲池" + idleConnections.size() + "," + "活跃池" + activeConnections.size();
}
}

3、测试使用

/**
* 数据源案例的测试
*/
public class DataSourceDemo {
/**
* 测试逻辑:(空闲max参数5,活跃max参数10) 建立30个线程,执行一个sql,然后每个线程执行完,打印一次池状态,
* 并且10次再打印一次池状态。
*/
public static void main(String[] args) throws SQLException {
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/java_base?characterEncoding=utf8";
String username = "root";
String password = "123456";
MyPoolDataSource pool = new MyPoolDataSource(driver, url, username, password);
excute(pool);
CyclicBarrier cb = new CyclicBarrier(10, () -> {
System.out.println("10个线程执行完:" + pool.logSate());
});
// 30个线程执行sql
for (int i = 0; i <30; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
excute(pool);
System.out.println("单个线程执行完:" + pool.logSate());
} catch (SQLException e) {
e.printStackTrace();
}
try {
cb.await();
} catch (Exception e1) {
e1.printStackTrace();
}
};
}, "thread" + i).start();
}
}
private static void excute(MyPoolDataSource pool) throws SQLException {
Connection cOnnection= pool.getConnection();
PreparedStatement prepareStatement = connection.prepareStatement("select * from blog");
ResultSet executeQuery = prepareStatement.executeQuery();
while (executeQuery.next()) {
// System.out.println(executeQuery.getObject("id"));
}
connection.close();
}
}

end!


推荐阅读
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 标题: ... [详细]
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
author-avatar
田小多
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有