1 前言

ObjectiveSQL 是一个Java ORM 框架,它不仅是Active Record 模式在Java 中的应用,同时还针对复杂SQL 编程提供近乎完美的解决方案,使得Java 代码与SQL 语句有机的结合,java培训改变了传统SQL 的编程模型(以字符串拼接为主的编程模型)。

ObjectiveSQL 项目分为两部分:一部分是运行期Maven 依赖 objective-sql 或 objsql-springboot,主要实现了基础的ORM 特性和SQL 编程模型,另一部分是IntelliJ IDEA 插件,兼容Java 运算符重载和动态代码提示。

ObjectiveSQL 主要解决:

  • 动态代码生成:基于领域模型(Domain Model),自动生成简单SQL 编程代码,使应用系统开发只关注自身的业务特性,提升开发效率
  • 可编程SQL:将SQL 中的控制原语、谓词、函数以及过程化逻辑等抽象为Java 中的高级类型,与Java 融为一体,使得SQL 成为真正过程化、逻辑型编程语言,可封装、可复用以及单元测试
  • 表达式语法一致性:Java 语法与SQL 语法等价替换,包括:数学计算、函数调用、比较与逻辑计算表达式,Java 表达式可以直接转换为SQL 表达式。

2 依赖安装

2.1 IntelliJ IDEA 插件安装

Preferences/Settings -> Plugins -> Search with "ObjectiveSql" in market -> Install

2.2 Maven 集成

独立应用程序,请将下列代码添加至dependencies:

<!-- In standalone -->
<dependency><groupId>com.github.braisdom</groupId><artifactId>objective-sql</artifactId><version>{objsql.version}</version>
</dependency>
复制代码

Spring Boot 集成项目,请将下列代码添加至dependencies:

<!-- In Spring Boot, you need add spring-jdbc dependency before -->
<dependency><groupId>com.github.braisdom</groupId><artifactId>objsql-springboot</artifactId><version>{objsql.version}</version>
</dependency>
复制代码

最新版本请访问 ObjectiveSQL,ObjSqlSpringBoot

2.3 Maven Compiler 参数配置

请将下列代码添加至pom.xml 中的 / 结点下:

<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.0</version><configuration><source>8</source><target>8</target><encoding>UTF-8</encoding><compilerArgs><arg>-Xplugin:JavaOO</arg></compilerArgs><annotationProcessorPaths><path><groupId>com.github.braisdom</groupId><artifactId>objective-sql</artifactId><version>${objsql.version}</version></path></annotationProcessorPaths></configuration>
</plugin>
复制代码

3 数据库连接注入

3.1 独立应用系统

以MySQL 为例,基于 ConnectionFactory 构造数据连接获取逻辑,并将其注入Databases。

private static class MySQLConnectionFactory implements ConnectionFactory {@Overridepublic Connection getConnection(String dataSourceName) throws SQLException {try {String url = "jdbc:mysql://localhost:4406/objective_sql";String user = "root";String password = "******";return DriverManager.getConnection(url, user, password);} catch (SQLException e) {throw e;} catch (Exception e) {throw new IllegalStateException(e.getMessage(), e);}}
}Databases.installConnectionFactory(new MySQLConnectionFactory());
复制代码

getConnection 方法中的的 dataSourceName 参数仅在多数据源的场景下使用,getConnection 方法可以根据不同的 dataSourceName 返回不同的数据库连接,其它场景下可以忽略该参数。

3.2 集成Spring Boot

应用系统基于Spring Boot 框架开发时,无需手动注入数据源,请按下列方法进行配置即可:

spring:profiles:name: objective-sql-exampleactive: developmentdatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:4406/objective_sqlusername: rootpassword: ******hikari:idle-timeout: 10000maximum-pool-size: 10minimum-idle: 5pool-name: Master# Configurations for multiple databasesextensions:# The name of data source, which will match with @DomainModel definition slave:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:4406/objective_sqlusername: rootpassword: ******hikari:idle-timeout: 10000maximum-pool-size: 10minimum-idle: 5pool-name: Slave
复制代码

其中 extensions 标记仅当多数据源时需要配置,而slave 作为数据源名称,应该与DomainModel 中定义的数据源名称匹配,或者通过DomainModelDescriptro 中动态数据源名称匹配。

4 简单SQL 编程指南

ObjectiveSQL 提供的简单SQL 编程主要针对单表的相关SQL 使用,通过动态生成的Java API 为应用系统的开发提供便捷的开发体验。

4.1 命名约定

4.1.1 类名与表名

缺省情况下,ObjectiveSQL 以驼峰与下划线的形式对Java 元素与数据库元素进行互相转换,示例如下:

1)Java 定义如下:

class Member {private String memberNo;private String name;
}
复制代码

2)数据库表定义如下:

create table members (member_no varchar not null,name varchar
);
复制代码

类名:Member 在数据库中对应的名称为 members,而字段名memberNo 对应的列名为 member_no,而字段名name 没有任何变化

4.1.1 关联对象

1)Java 定义如下:

class Member {private String memberNo;private String name;@Relation(relationType = RelationType.HAS_MANY)private List<Order> orders;
}class Order {private String no;private Long memberId;@Relation(relationType = RelationType.BELONGS_TO)private Member member;
}
复制代码

2)数据库表定义如下:

create table members (member_no varchar not null,name varchar
);create table members (member_no varchar not null,member_id int(10) not null,name varchar
);
复制代码

通过上面的结构定义,可以看出几个关键特征:

  • 用于承载HAS_MANY 关联对象的实例变量members 是由类型转换成复数,而BELONGS_TO 与HAS_ONE则为单数
  • Order 类中存在一个外键对应的实例变量memberId,同时在表中也存在一个member_id与其对应
  • 其它规则与类与表转换的规则一致

注意: 所有类名在转换为复杂时,遵循英文的规律,例如:person 对应 pepole

4.2 领域模型定义

@DomainModel
public class Member {@Size(min = 5, max = 20)private String no;@Queryableprivate String name;private Integer gender;private String mobile;@Transientprivate String otherInfo;@Relation(relationType = RelationType.HAS_MANY)private List<Order> orders;
}
复制代码

ObjectiveSQL 会根据上述模型定义,自动生成基础的SQL 编程相关方法和SQL 抽象模型定义

4.3 数据查询

Member.countAll();
Member.count("name = ?", "braisdom");Member.queryByPrimaryKey(1);
Member.queryFirst("id > ?", 1);
Member.query("id > ?", 1);
Member.queryAll();
复制代码

4.4 数据更新

Member.create(newMember);
Member.create(newMember, true); // Create a member without validating
Member.create(Member.newInstanceFrom(memberHash));
Member.create(new Member[]{newMember1, newMember2, newMember3}, false);Member.update(1L, newMember, true); // Update a member by primary key and skip validationg
Member.update("name = ?", "name = ?", newName, oldName);Member.destroy(1L); // Delete a member by primary key
Member.destroy("name = ?", "Mary");
复制代码

4.5 事务

4.5.1 基于Annotation 的事务

// The method will be executed in a database thransaction
@Transactional
public static void makeOrder(Order order, OrderLine... orderLines) throws SQLException {Order.create(order, false);OrderLine.create(orderLines, false);
}
复制代码

4.5.2 手动事务管理

// Transaction executing manually
Databases.executeTransactionally(((connection, sqlExecutor) -> {Member.update(1L, newMember, true);Member.update("name = ?", "name = ?", newName, oldName);return null;
}));
复制代码

4.6 关联对象查询

Member.queryAll(Member.HAS_MANY_ORDERS);
Member.queryFirst("id > ?", Member.HAS_MANY_ORDERS, 1);
Member.query("id > ?", Member.HAS_MANY_ORDERS, 1);Member.queryByPrimaryKey(1, Member.HAS_MANY_ORDERS);
Member.queryByName("braisdom", Member.HAS_MANY_ORDERS);
复制代码

上述代码中的 Member.HAS_MANY_ORDERS 属性为ObjectiveSQL 自动生成,在特殊情况下,可以基于 com.github.braisdom.objsql.relation.Relationship 自定义关联关系的构建逻辑。

4.7 分页查询

// Create a Page instance with current page and page size
Page page = Page.create(0, 10);
PagedList<Member> members = Member.pagedQueryAll(page, Member.HAS_MANY_ORDERS);
PagedList<Member> members = Member.pagedQuery(page, "name = ?", "braisdom");
复制代码

4.8 Query 接口编程

Query query = Member.createQuery();
query.project("name").groupBy("name").having("COUNT(*) > 0").orderBy("name DESC");List<Member> members = query.execute(Member.HAS_MANY_ORDERS);// Paged querying with querying dynamically
Paginator paginator = Databases.getPaginator();
Page page = Page.create(0, 10);
PagedList<Member> pagedMembers = paginator.paginate(page, query, Member.class, Member.HAS_MANY_ORDERS);
复制代码

针对SQL 中的分组和排序,需要通过Query 接口完成,同时Query 接口也可以进行分页和关联对象查询。

4.9 Validation

ObjectiveSQL Validation 内部集成了Jakarta Bean Validation

详细使用方法请参考:beanvalidation.org/

4.9.1 手工调用 validate 方法

Member newMember = new Member().setNo("100").setName("Pamela").setGender(1).setMobile("15011112222");// Violations occurred in field 'no'
Validator.Violation[] violations = newMember.validate();
复制代码

4.9.2 创建对象时 validate

Member newMember = new Member().setNo("100000").setName("Pamela").setGender(1).setMobile("15011112222");Member.create(newMember);
Member.create(newMember, true); // Skip validation
复制代码

4.10 自定义SQL

Member.execute("DELETE FROM members WHERE name = ?", "Pamela");
复制代码

5 复杂SQL 编程指南

ObjectiveSQL 提供的复杂SQL 编程,其实是对SQL 语法的一种抽象和建模,以Java API 形式进行互相作用,使得复杂SQL 不再以字符串的形式出现在Java 中,从而实现动态化SQL 变得清晰易理解,不同的业务系统也可以基于ObjectiveSQL 对自身业务的再抽象和建模,实现SQL 逻辑的复用。

5.1 JOIN 查询

5.1.1 隐式 Join

Member.Table member = Member.asTable();
Order.Table order = Order.asTable();Select select = new Select();select.project(member.no, member.name, count().as("order_count")).from(member, order).where(member.id.eq(order.memberId)).groupBy(member.no, member.name);List<Member> members = select.execute(Member.class);
复制代码
SELECT `T0`.`NO` , `T0`.`name` , COUNT(*) AS `order_count`
FROM `members` AS `T0`, `orders` AS `T1`
WHERE (`T0`.`id` = `T1`.`member_id` )
GROUP BY `T0`.`NO` , `T0`.`name`
复制代码

5.1.2 显式Join

Member.Table member = Member.asTable();
Order.Table order = Order.asTable();Select select = new Select();select.project(member.no, member.name, count().as("order_count")).from(member).leftOuterJoin(order, order.memberId.eq(member.id)).groupBy(member.no, member.name);List<Member> members = select.execute(Member.class);
复制代码
SELECT `T0`.`NO` , `T0`.`name` , COUNT(*) AS `order_count`
FROM `members` AS `T0`
LEFT OUTER JOIN `orders` AS `T1` ON (`T1`.`member_id` = `T0`.`id` )
GROUP BY `T0`.`NO` , `T0`.`name`
复制代码

5.2 分页查询

Member.Table member = Member.asTable();
Order.Table order = Order.asTable();Paginator<Member> paginator = Databases.getPaginator();
Page page = Page.create(0, 20);Select select = new Select();select.project(member.no, member.name, count().as("order_count")).from(member, order).where(member.id.eq(order.memberId)).groupBy(member.no, member.name);PagedList<Member> members = paginator.paginate(page, select, Member.class);
复制代码
-- Counting SQL
SELECT COUNT(*) AS count_
FROM (SELECT`T0`.`NO`,`T0`.`name`,COUNT(*) AS `order_count`FROM `members` AS `T0`, `orders` AS `T1`WHERE (`T0`.`id` = `T1`.`member_id`)GROUP BY `T0`.`NO`, `T0`.`name`
) T
复制代码
-- Querying SQL
SELECT `T0`.`NO`, `T0`.`name`, COUNT(*) AS `order_count`
FROM `members` AS `T0`, `orders` AS `T1`
WHERE (`T0`.`id` = `T1`.`member_id`)
GROUP BY `T0`.`NO`, `T0`.`name`
LIMIT 0, 20
复制代码

5.3 复杂表达式查询

ObjectiveSQL 通过运算符重域技术使得Expression 也可以参与各类运算符计算,从而使得Java 代码变得简单易懂,而不是通过各类运算符方法进行计算。ObjectiveSQL 表达式计算时并不能够与SQL 表达完匹配,默认情况下所有表达式均可以进行算术运算,在IntelliJ IDEA 中并不能给出完整的提醒,例如:JoinExpression 也可以进行算术运算,此时在IntelliJ IDEA 中并不会出现语法错误的提醒,但在执行运算过程中会抛出 UnsupportedArithmeticalException,该异常为RuntimeException 的子类。

Order.Table orderTable = Order.asTable();
Select select = new Select();select.project((sum(orderTable.amount) / sum(orderTable.quantity) * 100).as("unit_amount")).from(orderTable).where(orderTable.quantity > 30 &&orderTable.salesAt.between("2020-05-01 00:00:00", "2020-05-02 23:59:59")).groupBy(orderTable.memberId);List<Order> orders = select.execute(Order.class);
复制代码
SELECT ((((SUM(`T0`.`amount` ) / SUM(`T0`.`quantity` ) )) * 100)) AS unit_amount
FROM `orders` AS `T0`
WHERE ((`T0`.`quantity` > 30)AND `T0`.`sales_at` BETWEEN '2020-05-01 00:00:00' AND '2020-05-02 23:59:59' )
GROUP BY `T0`.`member_id`
复制代码

5.4 动态查询

所谓动态查询,实际上就是表达式的构建过程跟随着参数的有无而变化,基于这种使用场景,ObjectiveSQL 设计了一个永真的逻辑表达式EternalExpression ,永真表达式是程序上的一种巧妙设计,使得代码逻辑变得更清晰,即使所有参数均未赋值,整个表达式也会存在一个永的表达,确保最终SQL 语句的正常。

String[] filteredNo = {"202000001", "202000002", "202000003"};
int filteredQuantity = 0;Order.Table orderTable = Order.asTable();
Select select = new Select();
LogicalExpression eternalExpression = new EternalExpression();if(filteredNo.length > 0) {eternalExpression = eternalExpression.and(orderTable.no.in(filteredNo));
}if(filteredQuantity != 0) {eternalExpression = eternalExpression.and(orderTable > filteredQuantity);
}select.project((sum(orderTable.amount) / sum(orderTable.quantity) * 100).as("unit_amount")).from(orderTable).where(eternalExpression).groupBy(orderTable.memberId);List<Order> orders = select.execute(Order.class);
复制代码
SELECT ((((SUM(`T0`.`amount` ) / SUM(`T0`.`quantity` ) )) * 100)) AS unit_amount
FROM `orders` AS `T0`
WHERE ((1 = 1) AND `T0`.`NO` IN ('202000001', '202000002', '202000003') )
GROUP BY `T0`.`member_id`
复制代码

6 高级使用

6.1 日志集成

由于 ObjectiveSQL 无法决定应用系统使用哪一个日志框架,所以ObjectiveSQL 并未集成任何第三方日志框架,确认使用JDK 自身的日志框架,如果应用系统需要使用自身的日志框架,并在系统启动完成后注入ObjectiveSQL,请按下列方式集成(以Slf4j 为例)。

6.1.1 LoggerFactory 扩展实现

public class ObjLoggerFactoryImpl implements LoggerFactory {private class ObjLoggerImpl implements Logger {private final org.slf4j.Logger logger;public ObjLoggerImpl(org.slf4j.Logger logger) {this.logger = logger;}@Overridepublic void debug(long elapsedTime, String sql, Object[] params) {logger.debug(createLogContent(elapsedTime, sql, params));}@Overridepublic void info(long elapsedTime, String sql, Object[] params) {logger.info(createLogContent(elapsedTime, sql, params));}@Overridepublic void error(String message, Throwable throwable) {logger.error(message, throwable);}private String createLogContent(long elapsedTime, String sql, Object[] params) {String[] paramStrings = Arrays.stream(params).map(param -> String.valueOf(param)).toArray(String[]::new);String paramString = String.join(",", paramStrings);return String.format("[%dms] %s, with: [%s]",elapsedTime, sql, String.join(",",paramString.length() > 100 ? StringUtil.truncate(paramString, 99) : paramString));}}@Overridepublic Logger create(Class<?> clazz) {org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(clazz);return new ObjLoggerImpl(logger);}
}
复制代码

6.1.2 普通应用程序注入方式

public class Application {public static void main(String[] args) {Databases.installLoggerFactory(new ObjLoggerFactoryImpl());// others}
}
复制代码

6.1.3 Spring Boot 应用程序注入方式

@SpringBootApplication
@EnableAutoConfiguration
public class Application {public static void main(String[] args) {SpringApplication springApplication = new SpringApplication(Application.class);springApplication.addListeners(new ApplicationListener<ApplicationReadyEvent>() {@Overridepublic void onApplicationEvent(ApplicationReadyEvent event) {Databases.installLoggerFactory(new ObjLoggerFactoryImpl());}});springApplication.run(args);}
}
复制代码

6.2 基于SQL 语句的对象缓存

应用系统中对时间性不强的数据会进行数据缓存,通常会将数据缓存至Redis 中,针对些特性,可以扩展ObjectiveSQL 的 SQLExecutor 接口轻易实现。

6.2.1 SQLExecutor 扩展实现

public class CacheableSQLExecutor<T> extends DefaultSQLExecutor<T> {private static final List<Class<? extends Serializable>> CACHEABLE_CLASSES =Arrays.asList(new Class[]{Member.class});private static final Integer CACHED_OBJECT_EXPIRED = 60;private static final String KEY_SHA = "SHA";private Jedis jedis = new Jedis("localhost", 6379);private MessageDigest messageDigest;public CacheableSQLExecutor() {try {messageDigest = MessageDigest.getInstance(KEY_SHA);} catch (NoSuchAlgorithmException e) {throw new IllegalArgumentException(e.getMessage(), e);}}@Overridepublic List<T> query(Connection connection, String sql,TableRowAdapter tableRowAdapter, Object... params) throws SQLException {Class<?> domainClass = tableRowAdapter.getDomainModelClass();if (CACHEABLE_CLASSES.contains(domainClass)) {if(!Serializable.class.isAssignableFrom(domainClass)) {throw new IllegalArgumentException(String.format("The %s cannot be serialized"));}messageDigest.update(sql.getBytes());String hashedSqlId = new BigInteger(messageDigest.digest()).toString(64);byte[] rawObjects = jedis.get(hashedSqlId.getBytes());if (rawObjects != null) {return (List<T>) SerializationUtils.deserialize(rawObjects);} else {List<T> objects = super.query(connection, sql, tableRowAdapter, params);byte[] encodedObjects = SerializationUtils.serialize(objects);SetParams expiredParams = SetParams.setParams().ex(CACHED_OBJECT_EXPIRED);jedis.set(hashedSqlId.getBytes(), encodedObjects, expiredParams);return objects;}}return super.query(connection, sql, tableRowAdapter, params);}
}
复制代码

6.2.2 注入方式

public class Application {public static void main(String[] args) {Databases.installSqlExecutor(new CacheableSQLExecutor());// others}
}
复制代码

Spring Boot 的注入方式去 LogFactory 的注入方式相同

6.3 ColumnTransition 扩展

ColumnTransition 是ObjectiveSQL 对外提供的一种数据类型转的扩展接口,该接口的详细定义请参考:ColumnTransition.java ,以日期形式为例,介绍ColumnTransition 的扩展方式。

public class SqlDateTimeTransition<T> implements ColumnTransition<T> {@Overridepublic Object sinking(DatabaseMetaData databaseMetaData, T object,TableRowAdapter tableRowDescriptor, String fieldName, FieldValue fieldValue) throws SQLException {String databaseName = databaseMetaData.getDatabaseProductName();if (fieldValue != null && fieldValue.getValue() != null) {if (SQLite.equals(databaseName) || Oracle.equals(databaseName)) {return fieldValue;} else if (PostgreSQL.equals(databaseName)) {if (fieldValue.getValue() instanceof Timestamp) {return fieldValue.getValue();} else if (fieldValue.getValue() instanceof Long) {Instant value = Instant.ofEpochMilli((Long) fieldValue.getValue());return Timestamp.from(value);} else {return Timestamp.valueOf(String.valueOf(fieldValue.getValue()));}} else {return fieldValue;}}return null;}@Overridepublic Object rising(DatabaseMetaData databaseMetaData, ResultSetMetaData resultSetMetaData,T object, TableRowAdapter tableRowDescriptor, String columnName, Object columnValue) throws SQLException {String databaseName = databaseMetaData.getDatabaseProductName();try {if (columnValue != null) {if (SQLite.equals(databaseName)) {Instant value = Instant.ofEpochMilli(Long.valueOf(String.valueOf(columnValue)))return Timestamp.from(value);} else {return columnValue;}}} catch (DateTimeParseException ex) {String message = String.format("Invalid raw DataTime of '%s' from database: %s",columnName, columnValue);throw new IllegalArgumentException(message, ex);}return null;}
}
复制代码

sinking 方法是将Java 中的值,转换为数据库所能接受的值,rising则为将数据库中的值,转换为Java 所能接受的值。

作者:IT小尚
链接:https://juejin.cn/post/7041016816293904420
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

java开发之Java ORM 框架推荐相关推荐

  1. 框架开发之Java注解的妙用

    框架开发之Java注解的妙用 注解的好处: 1.能够读懂别人写的代码,特别是框架相关的代码. 2.本来可能需要很多配置文件,需要很多逻辑才能实现的内容,就可以使用一个或者多个注解来替代,这样就使得编程 ...

  2. 我的Java开发之路

    最近有一位小伙伴通过公众号给我留言, "我参加工作没多久,看着圈里的技术大牛,特别羡慕,也渴望成为技术大牛,想让您分享一下从小白到大牛是怎样练成的,我该如何提高自己" 首先,谢谢这 ...

  3. java微博开发_【新手入门篇】新浪微博应用开发之Java入门篇

    新浪微博应用开发之Java篇 2012年11月11日星期日 一.开发流程简介: 2.  在以下地址创建一个应用,假设创建一个桌面应用: 3.  进入管理中心,查看并记住应用ID和密码: 4.  在&q ...

  4. 【新手入门篇】新浪微博应用开发之Java入门篇

    新浪微博应用开发之Java篇 2012年11月11日星期日 一.开发流程简介: 1.  访问http://open.weibo.com/注册成为开发者 2.  在以下地址创建一个应用,假设创建一个桌面 ...

  5. webflow ajax,java开发之spring webflow实现上传单个文件及多个文件功能实例

    本文实例讲述了java开发之spring webflow实现上传单个文件及多个文件功能.分享给大家供大家参考,具体如下: 上传单个文件 准备 1. 如果你项目中使用了spring security的话 ...

  6. 新浪微博应用开发之Java入门篇

    新浪微博应用开发之Java篇 2016-10-30 一.开发流程简介: 1.  访问http://open.weibo.com/注册成为开发者 2.  在以下地址创建一个应用,假设创建一个桌面应用: ...

  7. java中轻量级数据库ORM框架:JOOQ

    1.使用maven下载 <!-- https://mvnrepository.com/artifact/org.jooq/jooq --><dependency><gro ...

  8. 常见java面试遇到的ORM框架理解

    常见ORM框架理解 前言: JAVA编程免不了和数据库打交道,那么如何高效便捷地操作数据库,也是一个需要而对的问题,原生的基于JDBC的方式当然是非常低效,而且要写一大堆无用的模板代码,不值得选取.好 ...

  9. JAVA开发之 1-JAVA语言发展史

    Java诞生于1991年,当时Sun公司有个称为Green项目,目的是开拓消费类电子产品市场,例如,交互式电视.烤面包箱等家用电器的控制软件.该小组的领导人是James Gosling(Java之父, ...

最新文章

  1. CXF 客服端调用报错
  2. ctrl导致开机弹出计算机,Win7系统开机黑屏提示Press Ctrl+Alt+Del to restart如何解决...
  3. opengl实现经纹理映射的旋转立方体_《图形编程技术学习》(五十三)环境映射...
  4. 表达式目录树(Expression)
  5. Git使用手册:HTTPS和SSH方式的区别和使用
  6. linux系统获取光盘信息api,在Visual C#中运用API函数获取系统信息
  7. Iocomp .net仿真仪表控件包
  8. JS实现60s倒计时(亲测有效),及span标签如何使用和禁用onclick事件
  9. js怎么实现ftp上传文件到服务器,js ftp上传文件到服务器
  10. python open gbk_python3 open txt的UnicodeDecodeError: 'gbk' codec问题解决方案
  11. mysql导出文件名乱码_快速解决mysql导出scv文件乱码、蹿行的问题
  12. 成功解决RecursionError: maximum recursion depth exceeded
  13. 12 项目收尾管理:项目验收、项目总结、系统维护、项目后评价
  14. 人生苦短——珍惜眼前人
  15. 【VBS发邮件】乱码解决方法
  16. 如何通过学习实现人生的逆袭!
  17. InterSystems开发者关系主管Dean:集成FHIR,改变游戏规则
  18. 【计算机网络】因特网通信
  19. 云看板生产管理系统,实时监控网关采集的数据
  20. 两大图灵奖得主力作:计算机架构的新黄金时代

热门文章

  1. Linux系统安装keras,keras安装
  2. vm虚拟机安装openwrt或群辉教程
  3. mysql主从 复制新库_关于MySQL主从复制的几种复制方式总结
  4. 如何理解JavaScript原型链
  5. java 数组赋值_java中为数组赋值的方法
  6. 解决启动浏览器显示hao123, 2345等网站首页的问题
  7. 中国建筑科学大会暨绿色智慧建筑博览会开幕!奥的斯新一代Gen3智慧电梯中国首发;格兰富、紫荆花重磅亮相 | 美通社头条...
  8. 安卓开发实战!斗鱼直播Android开发二面被刷,成功入职阿里
  9. 万能数据库查询分析器与EXCEL强强联合进行数据分析
  10. PMP学习一、项目组织结构