zoukankan      html  css  js  c++  java
  • hibernate深入学习笔记

    hibernate深入学习笔记

    在hb刚火的那正儿, 看过, 但是对ormaping不是很理解, 现在重新看hb, 以前很多不是很懂的地方现在基本已经全部豁然开朗.

    ·increment标识生成器由hibernate以递增的方式生成主键

    ·identity标识生成器由底层数据库来负责生成主键,这个主要针对支持自增字段作为主键的数据库

    ·sequence标识生成器由底层数据库提供的序列来生成主键

    ·native标识生成器会根据底层数据库来选择是使用increment,还是identity, 还是sequence来生成主键

    ·数据库中都是一对一, 或者多对一这种关系, 如果是单向关联, 一般是这样来设计关联关系

    ·如果数据库中的字段和java类的属性之间是一对一的关系的话, 那么hbm文件中使用property元素来表现, 否则就要使用其他的元素来表现了, 对于多对一,要使用many-to-one元素, 对于一对多就要使用set这样是元素, 比如订单和用户之间的多对一关联, order中有个customer属性, 但是数据库order表中没有customer的对应字段,在写hbm文件的时候就要使用many-to-one元素, 如果用户和订单之间是一对多的关系, customer中有一个orders属性, 但是数据库customer表中没有orders这样的字段,在写hbm文件的时候就需要使用set元素

    ·表和java类之间的关联关系的确定是根据实际需要来做的, 当需要根据一方来查找多的一方的情况,那么可以建立多对一映射, 如果需要根据多的一方取得一的一方, 则需要建立一对多映射

    ·在设置双向关联时,需要在customer和order两边都要进行设置, 例如这样写:customer.addOrders(order); order.setCustomer(customer);这样做了之后, 当hb发现customer发生变化之后会生成一条update语句, 当发现order也发生了变化则会生成另外一条语句. 为了提高性能, 需要将多的一般是inverse设置为true, 这样在设置双向关联时, 只会根据多的一方的状态变化来执行一条update语句

    ·如果需要级联删除, 那么需要在多的一方(set)设置cascade=delete, 而且这个删除只是在数据库中将相关的记录删除, 而此时的持久化对象还存在内存中, 只是不再与相关的表中的记录关联, 成了临时对象

    ·在一对多的关系中, 如果设置多的一方(set)的cascade属性为nnoe, 那么解除一个子对象和它的父对象之间的关联关系, 表现在数据库中只是将子表的外键设置为空, 而如果将cascade设置为all-delete-orphan的话, 则会删除子表中的相应记录

    ·session的commit方法会调用其flush方法对缓存进行清理, 一般我们不用调用flush方法,只要调用commit就好

    ·hb中对象的三种状态:临时(瞬时)状态, 刚新建, 还没有放到session缓存中; 持久化状态, 已经放到session缓存中;游离状态, 已经持久化, 在数据库中有对应的一条记录, 但是已经从session中清除, 对象的持久化状态是跟某个session相关的, 因此要避免一个持久化对象被两个session关联

    ·session可以说是hb中最重要的对象,它提供了一系列重要的方法
    save()方法用来把一个临时对象转换成一个持久化对象,所以把一个已持久化的对象或者一个游离状态的对象传给save作为参数, 这样做是没有意义的, 前者什么都不会做, 后者会重新添加一条记录

    ·session执行save()方法并不立即执行sql语句,只有在清理缓存的时候才会执行sql, 所以如果在save之后又修改了持久化对象, 会又产生一条update的sql语句, 因此必须把所有的修改放在save方法之前

    ·update()方法把一个游离状态的对象转换成一个持久化对象, 只会在session被刷新时才会执行update的sql语句, 因此对游离对象的多次修改只会生成一条sql语句, 而且即使没有修改游离对象的任何属性, 在update的时候也会生成一条update的sql语句, 如果在hbm文件中把select-before-update属性设置为true,那么在执行update之前会先执行一条select语句,然后把得到的结果与当前的对象进行比对, 如果发生变化再执行update语句

    ·如果缓存中存在一个与当前要添加的游离对象相同OID的持久化对象,那么执行update的时候会抛出异常.

    ·session的saveOrUpdate方法()同时包含了save和update方法的功能, 如果传入的是游离对象就调用update方法, 如果是临时对象就调用update方法, 如果是持久化对象,则直接返回

    ·hb认为一个对象是临时对象的条件,id值为null; 有version属性且为null; id有unsaved-value属性,且id与该值一致;指定了Intercepter类,且isUnsaved返回ture

    ·load方法和get会根据给定的id返回一个持久化对象, 区别在于在数据库中没有找到对应的记录,前者会抛ObejctNotFoundException的异常, 后者返回null, load返回的是实体类的代理对象, get返回的就是实体类, load在一级缓存中查找, 找不到会接着在二级缓存中找, 而get只会在一级缓存中查找.

    ·delete方法会删除一个持久化对象, 如果删除的是一个游离对象, 那么hb会将其变成持久对象,然后再删除, 只有在清缓存的时候才会生成删除记录的sql语句, 而在session关闭的时候才会从session中清除删除对象

    ·在使用了set, many-to-one这种级联映射, 需要设置cascade属性来执行不同的级联操作

    ·如果数据库使用了触发器, 那么使用hb的时候会导致session得到的对象与数据库不一直, 这时需要立即使用session的refresh()再取一次, 得到执行触发器之后更新的对象

    ·hb的interceptor可以看成数据库的触发器

    ·hb中自定义类型, 简单的说就是在使用ResultSet从数据库中取到的值进行类型转换, 得到最终需要的类型, 而插入到数据库的时候, 再一次对值进行转换, 然后使用Statement添加到数据库中

    ·在自定义数据类型的时候,一般要将自定义的类型定义为不可变的

    ·为了避免不必要的访问数据库, hb在检索方面使用了两种检索策略:延迟检索(避免执行不必要的关联检索)和强制左连接(使用一条sql语句将当前对象和关联对象同时取出)

    ·检索又分为类级别的检索和关联级别的检索
    ·类级别的检索是这样的, 执行session的load和get方法时, 立即或者延迟得到相应的对象, 其hbm文件的设置是在class元素上添加lazy属性为false和true, 延迟的做法是这样的, 不会理解返回一个对象, 而仅仅只是取得其OID属性值生成一个非常简单的继承映射对象的代理对象(这个是CGLIB来做的),只有在真正需要访问该对象的属性的时候才执行sql语句取得该对象的所有属性

    ·类级别的延迟加载会对session的一些行为带来以下影响, 如果load没有找到对象,不会抛出ObjectNotFoundException, 只有在使用该对象的get方法的时候才会抛出该异常 ;如果在load之后,得到持久化对象没有进行任何get操作, 然后将其变成游离对象之后, 那么该游离对象除了id值之外(调用id的get方法是不会导致代理类实例化的), 其他值均为不可访问的.访问这些属性的get方法会抛出异常, Hibernate的initialize方法会立即对代理类进行初始化处理

    ·延迟加载只对laod有效, 对get和find是无效的, 因此get永远不会生成映射类的代理类

    ·批量立即检索的原理:set的batch-size属性是指在多对多和一对多的关联的情况下, 使用find找出找出主表记录之后, 在查找关联表中的记录的时候, 一次使用多少条主表中的id来执行查询, 默认情况下, 每次利用主表中一条记录的id找出从表中对应的记录, 这样的sql语句会明显增加, 使用batch-size则会减少查询从表的次数

    ·强制左连接只对get方法有效, 对find是无效的, 当设置了set的outer-join为true之后, hb会使用一条语句同时取得主从关联表中的相关记录

    ·对于多对一或一对一关联, 应该优先采用外连接检索策略, 这样比立即检索策略使用的sql语句更少

    ·如果外连接表的数目太多, 也会影响检索的性能, 可以通过设置hibernate的max_fetch_depth的值来设置左链接表的数目, 这个值的设置取决于表中记录的数目以及数据库外连接的性能

    ·对于一对一的关联, 如果要使用延迟加载策略, 则需要设置one-to-one中的constrained为true

    ·hb的检索方式有通过对象图导航检索(session的load,get方法), 通过HQL检索(session的find和Query的query方法, 建议后者优先), 通过QBC检索(通过Criteria以对象的方式检索), 通过QBE检索, 本地SQL检索

    ·在QBC中可以通过this引用当前实例.

    ·HQL和QBC支持支持多态, 比如:from java.lang.Object, from java.io.Serializable将找出所有的实例, 以及实现了Serializable接口的实例

    ·HQL和QBC中分页查询实现
    ·hb提供了两个方法来实现分页查询, 第一个是setFirstResult(int firstResult), 设定从哪个对象开始检索, 参数firstResult表示这个对象在查询结果中的索引位置.默认为0; setMaxResult(int maxResults)设置第一次最多检索出的记录数

    ·注意在hb中很多方法都是支持方法链编程风格的

    ·取得多个对象使用list()方法, 取得单个对象使用uniqueResult()方法, 如果查询返回结果中可能有多个对象, 那么可以使用setMaxResult(1)将返回结果设置为1, 然后再调用uniqueResult()方法

    ·在参数中绑定位置信息,hb绑定参数有两种方式, 一种是冒号加参数名, 然后使用setXxxx("参数名", 参数值), 另一种是在HQL中使用?, 然后使用setXxxx(序号, 参数值);

    ·另外还有三种绑定参数的方法, 一种是setEntity("参数名", 持久化或游离对象), 另一种是使用setParameter("参数名", 参数值, hb映射类型名), 最后一种是将参数名与一个实体对象的属性绑定, 比如"from Customer as c where c.name=:name and c.age=:age", setProperties(customer)会将customer实体的name和age属性值与HQL中的name和age参数绑定

    ·如果HQL语句比较复杂, 那么推荐在hbm映射文件中编写HQL语句, 格式为<query name="queryName"><![CDATA[query string]]</query>,它将与class节点定义并列, 在程序中将通过Session的getNamedQuery()获得该查询语句, 这样的做法对维护性和可读性是一个不错的选择. 如果是本地sql语句那么需要这么写:<sql-query name="queryName"><![CDATA[query string]]></sql-query>, 而取查询语句的方法一律通过Session.getNamedQuery()方法取得

    ·HQL检索语句的写法中需要注意的一点:如果要查询所有customer表中name为null的记录, 需要这样写"from customer as c where c.name is null", 而不能写成"from customer as c where c.name=null", 因为不管name为何值, c.name=null不会返回true和false, 而是null

    ·HQL中的查询语句是不区分大小写的, QBC不支持直接调用sql函数, 不支持数学运算

    ·HQL中的范围运算跟sql类似, 使用between...and...和in(n1, n2, n3...)

    ·HQL中的模糊查询也跟sql类似, 一种是%表示任意长度, 任意字符, 如果是中文的话, 需要给两个%, 还有一种就是下划线, 举个例子就明白了, 比如要查询以T开头, 且字符串长度为3的所有客户:"from customer c where c.name like 'T_ _'", 来个复杂点的, name以T开头, 以m结尾的所有记录"from customer c where c.name like 'T%' and c.name like '%m'"

    ·HQL中的连接查询, 如果在hql语句中使用了"left join fetch", 那么将忽略映射文件中制定的检索策略而使用迫切左连接的检索策略, 如:"from customer c left join fetch c.orders o where c.name like 'T%'".使用迫切外连接有一个不好的地方是如果从表会有多条记录与主表中的一条记录关联, 那么会得到重复的主表中的记录, 为了过滤主表记录, 需要使用HashSet进行过滤

    ·左外连接在hb中的关键字是left join, 他和迫切左连接生成的sql语句是一样的, 不同之处是返回的对象是一个数组, 第一个是主表映射的对象实例, 第二个是关联表映射实例, 比如customer和order关联, list返回的每一个实例是一个数组, 包含了一个customer和order实例, 如果set order检索策略是延迟检索, 那么在执行customer.getOrders()方法时, 会生成相应的sql只是不会做真正访问数据库,而是从session缓存中取得相应的order

    ·一般我们写HQL语句都是从form开始, 当然也可以从select开始, 在单表操作中写不写select没有什么区别, 如果是多表关联操作就要注意了, 因为如果没有写select会同时主表和关联表的映射实例, 但是写了select则只会返回指定的映射实例对象

    ·内连接在hb中的关键字是join或者inner join, 在sql中表示取出两个表中都存在的记录, 在hql中跟左连接是一样, 返回的一个对象数组的集合

    ·迫切内连接关键字是inner join fetch, 跟迫切左连接一样, 都是忽略映射文件中的检索策略, 将主表和关联表中的实例同时取出, 也会包含重复记录

    ·如果只需要实例中的某些属性, 就需要在HQL中使用select了, 返回的结果还可以封装成一个JavaBean, 并在HQL中直接使用, 比如:select new CustomRow(c.id, c.name, o.orderNumber) from Customer c join c.orders o where c like 'T%', 这里的CustomerRow就是自定义的一个JavaBean, 对查询结果进行了封装, 可以在返回结果中直接进行访问

    ·如果只需要取出集合中的部分数据, 除了在hql中进行指定外, 还可以使用session的createFilter(collection, hql), 第一个参数是集合, 第二个参数需要进行过滤的hql语句,比如:session.createFilter(customer.getOrders(), "where this.price>100 order by this.price")

    ·在hb中使用本地sql的写法是在sql中出现应用实例属性的时候使用{}包含起来即可,比如Session.createSQLQuery("select cs.id as {c.id}, cs.name as{c.name} from custom cs where cs.id = 1", "c", Customer.class)

    ·一般我们在取得查询集合的时候会使用list, 但是在某些情况下使用iterate方法会有一定的优化作用,比如在缓存中已经存在某个集合的所有记录, 在检索的时候使用iterate(),只会生成取得OID集合的sql语句, 然后根据这个OID集合从缓冲中取得所需要的记录, 如果不存在,再到数据库中去找


    ·hb中的事务管理, 尽管一个session中可以对应多个事务,但是推荐一个session对应一个事务, 而且只允许一个未提交的事务

    ·数据库中锁的定义

    ·共享锁, 用于读取数据操作, 非独占, 但不允许其他事务执行更新操作, 当执行一条select语句的时候就会加锁, 执行完毕解锁

    ·独占锁, 锁定的资源其他的事务既不能读取也不能修改, 当执行insert, update, delete的时候会加锁,事务结束解锁

    ·更新锁, 可以和共享锁同时存在, 只有执行更新操作的时候才会升级到独占锁

    ·死锁的形成, 其他事务等待某个事务的独占锁释放, 而被等待的事务又请求等待事务释放独占锁, 这样一个请求环

    ·session的缓存是一级缓存, sessionFactory的缓存是二级缓存

    ·对于session中的一级缓存可以使用clear和evict方法对缓存进行清理, 但是一般不推荐这样做, 因为这种方法并不能提高性能, 在大批量更新和删除数据的时候使用viect方法.一般要先执行session.flush(), 再执行session.evict()方法.这种情况下通常的做法是绕过hb, 直接通过sql来执行, 其做法是:tx = session.beginTransaction();Connection con = session.connection(); PrepareSatement stmt = con.prepareStatement("update...");stmt.executeUpdate();tx.commit();

    ·在hb2.1中, update方法一次只能更新一条记录, 没法进行批量更新, delete()方法也一样, hb会将要删除记录先加载到缓存中, 然后再执行delete操作, 所以也是不推荐的

    ·hb的二级缓存都是通过第三方缓存方案来提供的, 是进程范围的缓存

    ·表之间没有继承关系, 但是类之间却存在继承关系, 要在表和类之间进行映射就需要处理这种继承关系,有三种处理方式:将具体类映射为一个表; 将基类映射一张表; 每个类映射一张表.

    ·在hb中还支持多态查询, 这个是跟类的继承相关的.也就是对基类进行查询,即返回子类A也返回子类B, 还有一种就是多态关联, 也就是根据主表查询对应的关联子表, 返回的结果即包含子表A中的内容,也包含子表B中的内容

    ·对于具体类对应一张表的情况, 这种情况下是没法进行多态关联, 也不支持多态查询, 都需要进行手工处理, 因此主表的映射文件不用设置一对多映射关系, 而子表必须同时指定对应子类的父类的属性和表字段之间的映射关系

    ·对于使用一张映射多个子类的情况, 这时需要在表中用一个字段来对不同的子类进行区分, 因此就不需要不需要为子类创建专门的映射文件, 这时的主表能进行多态关联和多态查询了, 因此可以在主表所对应的映射文件中设置一对多映射关系, 在子表中需要使用discriminator元素来告诉hb那个字段是用来映射到不同的子类,还需要加上subclass, 用来指定对应的具体的子类

    ·最后一种就是存在继承关系的每个类对应一张表, 这里的数据库表描述了类之间的继承关系, 因此可以实现主表和从表之间的多态关联和多态查询, 为了在映射文件中描述类之间的继承关系, 需要在基类的class元素中使用joined-subclass元素来描述子类与数据库表之间的映射关系

    ·如果不喜欢在class中嵌入joined-subclass和subclass, 而在单独的文件中使用之, 那么需要在这些元素中加上extends来指定继承的基类, 此时在HibernateConfiguration中必须通过addClass加上这些添加的子类的class.

    ·对于继承关系之间的映射方式的选择, 如果需要多态查询和多态关联, 可以选择具体子类跟表对应的映射方式; 如果需要使用多态查询和关联, 而且子类包含的属性不多, 可以采用一张表对应一个继承类, 而如果要使用多态查询和关联, 而且子类中包含的属性很多, 则采用一个类对应一张表的映射方式

    ·java中的Set表示的是无重复的集合, List表示按索引进行排序, 有重复的集合.

    ·TreeSet集合会对加入其中的对象进行排序处理, 因此添加的元素必须实现Comparable接口, 否则执行add方法的时候会抛出异常

    ·HashSet会根据集合元素的hashCode进行排序, 具有很好的存取性能, TreeSet会根据集合元素实现的Comparable接口进行排序,或者根据制定的Comparator排序规则进行排序

    ·添加到TreeSet中元素在被修改之后不会重新进行排序, 最适合排序的是不可变类(其属性不可修改)

    ·List会对集合中的元素按索引进行排序, 如果需要按自然方式或者自定义的方式排序, 可以通过Collections的sort(List), sort(List, Comparator)方法来处理

    ·hb中的集合映射, 在hb中, java中Set类型对应映射文件中的Set元素, 如果某个字段值(非主键)不允许重复时, 就要考虑使用Set. 如果允许重复的话, 则可以使用Bag, 它对应Java中的List, 在映射文件中对应的元素是idbag, 它包含collection-id子元素, 用来指定子表的主键. idbag虽然允许对重复, 但是不会按照索引进行排序, 如果需要按照索引进行排序, 则可以使用List, 而且表中必须使用一个字段来保存索引顺序, 在映射文件中对应的元素是list, 与set相比, 多了一个index子元素. 如果实体对象包含的子对象是一个map, hb在映射文件中使用了map元素来与之对应, 该元素有一个index的子元素, 就是用来指定与map在键值对应的表字段.

    ·hb为了能对集合进行排序, 它提供了两个属性来指定不同的排序方式, 一种是通过sort来将从数据库中取得数据进行内存排序, 一种是通过order-by属性直接在数据库中进行排序, 如果是在内存中进行排序(针对set和map), 可以给sort指定natural, 表示按自然方式排序, 如果指定的是一个全限定的实现了Comparable接口的类名, 则采用指定的方式进行排序, 但是使用内存排序的时候, 对应实体的java集合类就必须实现SortedSet和SortedMap接口的子类, 因为hb在内部会进行相应的转换, 否则会报造型出错的异常

    ·映射一对一关联, 一对一关联有两种处理方式, 一种是外键映射, 一种主键映射, 外键映射是主表使用从表的主键作为外键跟从表一对一的关联,在hbm文件中, 主表的映射中使用many-to-one指定从表映射, 同时制定unique为true, 在从表映射中, 通过one-to-one来指定从表对主表的映射关系 , 这样实际上是建立了主从表之间的双向关联映射, 不过只能建立一次从表到主表的一对一映射, 而且默认的一对一关联映射采用的是迫切左连接的检索策略, 想一想也应该如此.

    ·如果是采用主键映射, 也就是从表和主表的主键相同, 主表中没有从表的外键, 此时主表的映射文件就应该通过one-to-one来设置与从表的映射关系, 从表中也要使用one-to-one来建立与主表的映射关系, 同时还要制定contrained为true, 同时从表的主键策略必须使用foreign关键字来指明从表的主键与主表相同

    ·映射单向多对多关联, 在数据库中多对多关联都要使用中间表来处理, 因此在映射文件中要使用set来建立与中间表之间的映射, 同时cascade属性必须设置为save-update, 不能包含级联删除, 因为子表的记录可能跟多个从表的记录关联.

    ·映射双向多对多关联, 在主从表的映射文件的两端同时使用set, 但是必须将一端set中inverse属性设置为true, 这样由另外一端来负责处理关联关系, 在保存的时候需要同时保存主从映射对象

    ·在单向一对多关系中, 如果多的一端跟一的一端存在外键关联, 那么当一的一端作为主控方的时候可能会因为外键不能为空的约束,而保存不成功, 而且还会通过先insert一条从表记录,然后用主表主键update从表中外键, 为了解决这个问题, 需要使用双向一对多(单向一对多 + 多对一), 并将主控权交给多的一端.

    ·hb中的锁分两种:悲观锁和乐观锁, 悲观锁就是利用数据库的锁机制来实现的,在LockMode中定义, 通过Criteria, Query, Session进行设置, 乐观锁就是针对修改的记录进行锁定, 而不是对整个表进行锁定, 要对当前修改记录进行锁定需要使用一个专门的字段来协助处理, 一般是通过version字段或者timestamp字段

    ·hb的query中的list和iterate取出集合的区别, list使用一条sql将所有记录一次取出, 不会从cache中取数据, 但是iterate会首选将所有记录的id取出来, 然后在cache中找有没有存在被cache的记录, 没有再执行相应的sql从数据库中将数据取出并持久化

    ·hb中的sessionFactory是线程安全的, 而session是线程不安全的

    ·在hb3中提供了对动态模型的支持, 即采用Map来作为实体模型, 在映射文件中通过entity-name来指定实体名, 这种做法是为了提供hb的灵活性, 不过由此也带来了不利的一面, 使得用户又回到了jdbc中setParameter()类似的开发方式中

    ·hb中clob和blob对象的处理, 在hb中, 针对不同的数据库需要进行不同的处理, 比如对于sql server来说, 直接映射成java.sql.Clob, java.sql.Blob类型. 并借助Hibernate.createClob()和Hibernate.createBlob()创建大字段对象, 然后保存, 但是Oracle则需要使用另外的方式来处理, 因为Oracle在插入一条Clob或Blob记录的时候, 必须先创建一个游标, 因此必须先添加一条Clob或Blob字段为空的记录, 然后通过update将Clob或Blob对象进行更新, 步骤为先创建空的大字段对象, Hibernate.createBlob(new byte[1]), Hibernate.createClob(" ");保存空记录之后, 执行session.flush()强制提交, 然后使用session.refresh(实体对象, LockMode.UPGRADE)强制hibernate执行selecte for update, 向大字段对象写内容, 再次执行session.save()执行保存, 为了更好的具有通用性, 可以通过自定义UserType来封装读写大字段对象

    ·在hb2中HQL只能执行查询操作, 在hb3中HQL可以进行delete和update处理了

    ·在需要使用内连接的hql来说, inner join fetch和inner join的效果是不同的, 前者会将从数据库中返回的结果立即填充到对应的实体对象中, 后者执行查询返回每一条记录是一个数组对象, 数组中对应的是映射的主从映射实体对象

    ·关于批量加载, class的batch-size属性用来设置批量加载的条件数目, 如果在一个session中有多条查询语句, 为了提高性能,可以将多个条件合成一条sql语句执行从而达到批量加载的目的

    ·session的find方法实际上是无法利用缓存的, 它对缓存只写不读.而iterate则会根据取得的id从缓存中先找匹配的记录,没有找到再执行数据库查询

    ·Query Cache能针对查询条件的结果进行缓存, 如果多次执行相同条件的查询, 那么只会在第一次访问数据库, 后面的查询则直接从cache中读取, 但是它有两个显示条件, 必须是完全一致的sql语句, 两次查询之间对应的数据库表没有发生改变, hb中默认该功能是关闭的, 如果要打开必须设置hibernate.cache.use_query_cache属性为true
    , 而且在每次创建Query对象时, 必须执行以下Query.setCacheable(true)方法

  • 相关阅读:
    JAVA:连接池技术说明以及MVC设计模式理解
    SoftLayer®凭借Flex Images™消融物理与虚拟服务器之间的界线
    InternetExplorer 表单及用户名密码提交
    devStack
    windows平台vhd磁盘文件挂载
    xen vhd操作工具source code研读
    windows 基于命令行制作vhd虚拟磁盘
    vhd镜像格式及vhd-util工具应用
    mongoDB windows reinstall add auth
    MongoDb Windows linux平台环境及主流编程语言驱动安装同时配置mongoDb的远程连接
  • 原文地址:https://www.cnblogs.com/pricks/p/1506702.html
Copyright © 2011-2022 走看看