将long类型变量num作为参数,实现计算该数字乘积的持续性的方法 persistence()?

优秀的编程模型可以让程序在不同SM数量的GPU中用有可移植性。

内存、显存,UM(Unified Memory)提供统一的地址映射,减少内存、显存之间的拷贝(mirroring)

讲述了nvcc编译异构代码的过程

驱动会在runtime将PTX编译成二进制,这叫做JIT编译。缺点是增加了application load time,优点是可以让应用可以获取驱动更新所带来的编译优化,比如应用编译的时间早于新驱动发布的时间,可以利用新驱动的优化。

旧的PTX总可以在新的compute capability上编译成二进制,但是可能用不上新硬件上的新功能,比如tensor core。

(这就解释了为什么TF 编译XLA的时候默认使用ptxas编到cubin,但是有个判断如果编不出cubin,可能当前硬件太新了,就把ptx丢给驱动去编)

没有显式的初始化函数,在任何一个(除了显示版本信息和报错的函数)runtime函数被调用的时候,进行初始化

runtime为每一个device创建一个context,这个context叫做primary context,是第一次调用runtime函数时初始化的。所有host端线程共享它。创建这个context 的过程中,前面所说的JIT 编译过程,如果需要的话,也会编译device代码,然后拷贝到device内存中。这些的发生是透明的。

线性内存的寻址空间在我们常见的场景下是48bit

由于上述接口可能会申请很大的内存,建议有个try - catch 的备选分配代码

如果一个区域的内存被反复访问,则可以把它看作persisting, 如果只访问一次,则可以看作streaming

也就是说可以从L2 cache中分离一部分出来做数据的持久化,只有这部分空间不被persisting使用的时候,normal\streaming的访问才可以使用它。

这个set-aside空间的大小可以通过接口调整。

当使用MPS的时候,set-aside大小只能由启动MPS的时候指定。

后续的几个小标题介绍了很多L2  set-aside cache的用法,用到的时候再仔细看。

在一些设备上的D2H H2D拷贝可以和kernel并行执行(见3.2.6.3,当计算与拷贝有数据依赖时,想要并行,必须用锁页内存)

在一些情况下锁页的内存拷贝更快,见3.2.5.2

注意,锁页host内存是稀缺资源,申请锁页内存更容易失败,而且它减少了操作系统可支配的内存从而会减少系统整体性能。

从write-combining 内存读取数据非常慢,因此它一般用于host只写的场景。

通过另一个flag可以将锁页内存地址映射到device地址空间。这种内存块就会有两个地址:host端、device端,分别用两种接口可以获得。一个例外是 .的情况,只有一个统一的地址。

这样从kernel中访问host内存并不能像访问device内存一样快。但是有以下优势:

1. 不必在device端分配内存、拷贝内存,数据传输是隐式的

2.不必使用streams来掩盖数据拷贝与kernel计算,这种overlap是自动进行的了

由于映射锁页内存是host和device公用的,我们就必须利用stream或event来同步,避免写后读、读后写等冲突

而且必须在支持这个功能的设备上。

以下操作可以互相并行:

以下这些device端操作对于host来说是异步的

通过将环境变量CUDA_LAUNCH_BLOCKING设为1,可以全局地关闭kernel launch的并行,这个功能是debug用的,不应该用于生产。(补充,这是因为这个变量只有在cuInit的时候才会被读取,进程中之后对他的修改是不会起作用的)

profiler(Nsight 等)的硬件计数器通常会使得kernel launch变成同步的,除非设置一下。如果host端内存不是锁页的,那么相关的Async的拷贝也会变成同步的。

一些设备支持这种并行。(3.2.6.1说的是host层面,这节是对于device来说的,它可同时做这两种事情)。如果涉及host内存,它必须是锁页的。

高版本硬件支持双向数据拷贝之间的overlap,也是需要锁页内存

应用程序通过stream来管理并行操作,stream是顺序执行的操作。

以下操作隔开的两个stream的操作不能同步:

还有一些其他有关check的条件

阐述了overlap行为与软件的写法以及硬件是否支持相关的并行有关。并举例如何优化。

显式API需要将图节点一个个地加入进去。

 




当图出现变化,比如拓扑、数据类型出现变化的时候,就需要重新初始化图,会带来一些开销。但是当只有一些参数(比如地址)变化的时候,就没必要重新初始化,提供了Graph Update接口来实现这种功能。
下面介绍了两种update的方式

cudaGraph_t对象不是线程安全的。用户必须保证不同线程不能同时访问同一个cudaGraph。




靠,上次写的东西没了,辣鸡博客系统!










首先讲述了warp、warp scheduler,warp内部线程处理分支的方法。Volta架构前后的不同。(独立的pc)


5.1总体的4个基础策略:
  • 最大化并行执行以提高使用率
  • 优化内存使用以达到最大内存吞吐
  • 优化指令使用以达到最大指令吞吐
  • 最小化内存thrashing(颠簸,内存抖动,反复换入换出)
 
那种策略的性能收益最高取决于应用本身的性能限制。性能的优化需要针对性能的瓶颈,否则可能一无所获。将kernel的浮点数计算吞吐和内存吞吐(通常内存吞吐更有意义)与硬件本身的理论上限进行比较,可以得知kernel的提升空间。



高层面应用应该最大化host、device以及它们之间的总线(PCIe)之间的并行度。使用异步的函数调用和stream。而且应该让不同的设备做它们擅长的工作:host做串行工作,device做并行工作。
对于并行工作来说,当线程之间需要同步的时候则会打断这种并行。有两种情况:需要同步的线程在同一个thread block中,则使用__sync_threads()就可以。又或者线程在不同的block中,则需要通过global mem来进行数据交换,还需要两个kernel调用来完成,第一个kernel写数据到global mem,第二个从global mem中读数据。第二种情况的开销要大的多,kernel launch的开销和访问内存的开销。因此我们需要尽量避免第二种情况,在将算法用CUDA programming model实现的时候就要考虑到。

在低层,应用应该最大化设备的多个SM之间的并行。或者通过多个stream来让多个kernel来并行地执行,从而最大化utilization


之后阐释了warp、指令级并行、流水等概念(英文不太好理解),解释了数据依赖对指令latency的影响。以及arithmetic intensity(操作数含有offchip数据的指令数,与,操作数不含offchip数据的指令个数,的比例)的影响



一个kernel使用的寄存器个数,对并行的warp(resident warp)个数有很大影响,举了个例子。所以,编译器会在最小化寄存器使用个数的同时,最小化寄存器spilling个数和指令个数。



5.3 最大化内存吞吐
首先要最小化host到device的数据搬运,因为带宽小的多。


share mem是软件可控的cache,而对于一些应用来说,有时用传统的硬件cache使用方式有可能更好(比如数据局部性本身就很强)
然后就是global memory的访存模式要优化,这里5.3.2会介绍。

将尽可能多的代码使用device而不是host实现,即使一些kernel可能并行度并不是最适合使用GPU来执行。中间的数据结构可能整个生命周期都在device端而从未在host端出现。
将多次小的数据搬运合并成少量大的数据搬运
在有front-side bus的系统中,可以使用锁页内存
在使用mapped pagelocked memory的时候,没有必要在device端再分配一块内存然后再把数据拷贝过去,拷贝是隐式进行的。条件是mapped pagelocked mem只访问很少的次数。这些前面讲过。


不同的thread 中的内存访问指令,可能会被不同thread执行多次。不同类型的内存,其内存分布对访存的影响不同。例如对于global memory,内存越分散,吞吐越低。


当一个warp的指令访问内存时,他将warp中的thread的内存访问合并(coalesce)为一个或多个(取决于每个thread的访问大小和地址分布)transaction。总的来说,所需的transaction越大,不必要的多余数据传输越多,从而降低指令吞吐。例如,当transaction为32字节时,每个thread传输4字节,那么吞吐就会降低8倍。

因此要最大化内存吞吐,最大化内存合并就很重要,有以下几点:
  • 遵循最优访存模式,每个compute compability的介绍见附录K,(包含对于read-only数据的加速方法
  • 使用符合对齐要求的数据类型
  • 某些情况下对数据做padding
 

global memory支持数据大小为1,2,4,8,16字节的读写,当且仅当访问的数据类型是1,2,4,8,16字节且数据是自然对齐(地址是这个size的倍数)的时候,对global memory的访问才会被编译成一条指令。
如果这个size和对齐要求没有被满足,那么指令将会编译成多条有重叠的指令,不能被合并(coalesce)。所以推荐使用符合这个条件的数据类型。

对于结构体来说,可以用__align__来指定对齐
任何global memory中的变量地址,或通过驱动分配获得的地址,都是天然满足256字节对齐的
读取任何非自然对齐的8字节或16字节对齐的字,会导致错误的结果(有offset),因此需要特别注意。一个典型的场景就是自定义的地址分配,比如用一个调用来分配多维数组,此时每一小段都会有个offset,就不一定是对齐的了。

对二维数组的访问一般使用这样的公式来计算地址:
 

特别地,如果数组不满足上述需求,将它们pad成满足对齐的需求的数组,就会大大提高访问效率。cudaMallocPitch() and cuMemAllocPitch()就是方便做这种事情的。

  • 不能储存到寄存器中的大结构体
 


shared memory在片上,分bank,每个bank之间可以并行访问。如果n个访问访问不同bank,那么它们可以并行,如果访问同一个bank,那么它们发生了bank冲突,就需要串行。


在设备内存中,并使用const cache来缓存。


5.4 最大化指令吞吐
  • 避免使用吞吐低的指令,这包括在不影响精度的时候使用低精度。使用intrinsic 函数 (附录H)
  • 减少指令条数。包括减少同步点, ,使用__restricted__ (告诉编译器这几个变量是互相独立的,无关联的)
 

表格给出了各个计算指令的吞吐
 
 
 

要使得控制流指令对性能的影响最小,需要使得divergent warp的数量最少。比如,当条件分支取决于threadIdx / warpsize的时候,这时候每个warp内部的行为是一致的,就没有divergent warp。

有时编译器会将短的条件分支使用谓词(predicate)实现,这种情况下就不会出现divergent。每条指令都会执行,但只有predicate为true的指令才会生效。

 


5.5 最小化内存抖动
经常分配、释放内存的应用,可能会随着时间推移而变慢到一个limit。这在典型情况下是因为释放内存给操作系统导致的。(我自己的pytorch服务就有这种现象)可以注意以下几点:
  • 以合适的大小提前分配好空间,尽量减少程序中cudaMalloc cudaFree的出现次数,尤其是性能瓶颈的地方。
  • 如果一个应用程序不能分配足够的device内存,可以使用cudaMallocHost or cudaMallocManaged分配的其他内存类型。这样不是性能最好的,但程序起码能继续进行下去。
 




太多了,可以作为工具书来看。

有关于block间的同步机制








context像智能指针一样有一个计数器,减到0时候自动删除
}

// 默认方法是公共非抽象的实例方法。也就是Interface的default方法 // 根据返回值类型不同,调用不同执行方法,并返回不同结果 // 这部分,个人认为也可以采用一个私有方法进行处理。 // 这里为什么不作为私有方法处理。个人猜测:一方面是命名(命名与语义关联);另一方面是为了更直观展示other的处理方式,提高代码可读性?

这个部分,这里只是点一下。

c.补充:初始化过程与执行过程的关联

这里对上述相关类,进行阐述:

  • InitializingBean:利用Spring的生命周期接口,进行Mybatis对应Bean注入时间的时机控制(在配置注入完毕后)。详见

// 是否采取快速失败,用于在上下文刷新时,进行statement刷新
* 核心方法,由Spring的生命周期进行控制(钩子函数-配置设置后,进行触发)
// 各条件分支,对configuration进行配置的载入,以及不同级别日志的输出


这部分涉及SpringBoot的自动注入,从而达到拆箱即用的效果。



// 核心:环境(特指数据源环境,同一个Mybatis中,可以存在多个环境) // Mybatis自动映射行为(NONE、PARTIAL(不对嵌套结果类型进行映射)、FULL三种类型) // 默认反射工厂。详见反射模块部分 // 默认对象构建工厂。详见反射模块部分 // Mybatis插件实现,所依赖的责任链(暂不深入) // 核心:Mybatis类型处理器管理容器。详见类型模块 // 重要:缓存映射表,进行命名空间与其下对应缓存的映射

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

MP的实现,是基于Mybatis的。如果对Mybatis的源码有足够的认识,那么MP是很容易就入门的。
所以,这里不会对整个MP进行类似Mybatis的剖析。这里只针对其核心功能,进行实现的剖析。


* 没有XML配置文件注入基础CRUD方法
// 核心方法。进行定制化的通用mapper注入


* 插入一条数据(选择字段插入)
// 表包含主键处理逻辑,如果不包含主键当普通字段处理
// 按照格式要求,配合MethodType,构建对应的sql语句

有关Mybatis-Plus的lambda表达式的实现,涉及的点比较多。这里给一些建议:首先对函数式编程&流式编程有足够的了解,其次需要对wrapper的使用有一定认识,最后剖析Mybatis-Plus中对应部分。

最后,愿与诸君共进步。

  • vfs:虚拟文件系统,Mybatis用于加载服务器相关资源。具体作用,需要继续查看。其由一个抽象类VFS与两个实现类:JBoss6VFS与DefaultVFS,允许自定义实现(通过自定义VFS实现,解决springBoot的嵌套jar扫描问题)。
  • MyBatis的初始化可以有两种方式:
    • 基于XML配置文件:基于XML配置文件的方式是将MyBatis的所有配置信息放在XML文件中,MyBatis通过加载并XML配置文件,将配置文信息组装成内部的Configuration对象
  • MyBatis和数据库的交互有两种方式:

}

Android中的数据存储主要分为三种基本方法:

  2.传统文件系统。

  3.利用SQLite的数据库管理系统。

  对SharedPreferences对象和SQLite数据库,它们的数据仅对创建它们的应用是可访问的。

  换句话说,它们不是共享的,如果需要在不同的应用之间共享数据,需要建立content provider,本文对这部分内容不作介绍。

  本文基本是官方文档翻译,原文请见文后链接。

  选择内部或外部存储

  获取外部存储设备的权限

  向外部存储存储文件

三.在SQL数据库中存储文件

  向数据库中存入数据

  从数据库中读取数据

  删除数据库中的信息

   提供了一种存储键值对的方法,可以用于存储原生数据类型(boolean, float, int,

  这些数据在用户工作阶段一直被保持,即便应用被关闭也还是保持。

  所以可以用来保存一些数据,比如用户设定的字体,背景,用户名等,在下一次打开应用时,不用重新设定这些数据。

  获得对象可以使用下面两种方法:

  这是  中的方法,可以通过第一个参数指定你需要获得的文件名。

  向Shared Preferences写数据,首先需要调用方法创建一个 对象,然后调用这个对象的putXXX()方法存储键值对,键都是String类型,值是XXX所对应的数据类型(boolean,

  读取数据时需要调用SharedPreferences对象的getXXX()方法,来获得给定键(第一个参数)对应的值,如果给定键不存在,则会返回给定的默认值(第二个参数)。

  Android文件系统和其他平台上的类似,使用 APIs可以读写文件。

  这部分内容需要你已经了解了Linux文件系统的基础,并且也了解了包中的标准文件输入输出APIs。

  所有的Android设备都有两块文件存储区域:内部和外部存储。

  内部存贮一般指设备自带的非易失性存储器,外部存储指可拆卸的存储介质,比如微型的SD卡。

  一些设备把永久的存储区域分为"internal"和"external"的分区,所以即便没有可拆卸的存储介质,这些设备永远都有两种存储区域,并且不管外部存储区到底是可拆卸的还是内置的,APIs的行为是一致的。

  存储在内部存储区域的数据默认情况下只对你的app可用。无论是用户或者是其他app都不能访问你的数据。

  当用户卸载你的app时,系统会自动移除app在内部存储上的所有文件。

  不一定一直可以访问,因为用户可以拆卸外部存储设备。

  存储在外部存储的文件是全局可读的,没有访问限制,不受你的控制。可以和其他apps分享数据,用户使用电脑也可以访问在外部存储中的文件。

  当用户卸载你的app时,只有当你把文件存储在以 .获得的路径下时,系统才会帮你自动移除。

  注意:默认情况下app是安装在内存上的,可以通过在manifest中指定 属性来安排app的安装位置。具体见.

获取外部存储设备的权限

  为了向外部存储中写入数据,需要在manifest中指定权限  :

  注意:现在所有的app都可以读取外部存储中的数据,而不需要特殊的权限说明。

  但是,这点在新版本的更新中有可能会改变,如果你的应用需要读取外部存储中的数据而不需要写数据,应该声明 权限。

  为了保证应用能够持续工作,应该现在开始就加入读取权限:

  然而,如果你的应用包含了 权限,它隐式地包含了读取权限。

  向内部存储存文件时,可以通过下面两个方法获取合适的路径,返回值是一个File对象。

  返回一个File,表示的是app的应用文件在内部存储的绝对路径。

  返回一个File,表示的是app的缓存文件在内部存储的绝对路径。

  在如上路径中创建一个新文件,可以利用 的构造方法。将上面两个方法获得的File对象作为参数传入,如:

 

  另外,也可以调用 来获取 ,然后向你的内部目录写入数据,如下:

  或者,如果你需要缓存一些文件,你应该使用.

  比如,下面的例子从一个URL中提取了文件名,然后利用该文件名创建了一个文件存储在应用的内部缓存路径下:

 

  你应用的内部存储路径是由应用的包名指定的,在Android文件系统上的一个特定位置。

  技术上来说,如果你把文件的模式设置为可读的,其他应用是可以读取你的内部文件的,但是,另外的应用需要知道你的应用的包名和文件的名字。

  如果不明确指明,其他应用是不可浏览你的内部文件路径的,也不拥有读取和写入的权限。

  所以,只要你对内部存储上的文件使用,它们对其他应用来说就是不可用的。

  因为外部存储很有可能不可用,所以每次使用前都需要检查可用性。

  通过方法可以查询外部存储状态,如果返回状态为, 你可以继续对文件进行读写操作。

 

  尽管外部存储可以被用户和其他app修改,仍然有两种类型的文件你可以选择:

  这些文件对用户和其他应用都是可用的。当用户卸载应用时,这些文件对用户仍然是可用的。比如,应用拍摄的照片文件或者是下载的文件。

  属于你应用的文件,应当在应用被卸载的时候同时被删除。

  尽管从技术上来说,这些文件可以被用户和其他文件访问,因为它们是存储在外部存储介质上的,但是它们在你的应用外部并不提供什么实际价值。当用户卸载应用时,系统会删除外部存储上应用私有路径下的所有文件。

  比如,应用下载的一些额外的资源或者临时的媒体文件。

  存储公有文件,首先用获得路径,这个方法需要一个参数指明文件类型,比如  或 . 如:

  如果想要创建私有文件,利用 方法获得路径,并且传递给它一个名字指明路径类型。

  每一个用这种方法创建的路径都会被加在一个父目录中,这个目录包装了你的应用的所有外部文件,当你卸载应用时,系统会删除它们。

  比如,下面的方法为相册创建了一个路径:

  如果所有预定义的子目录都不适合于你的文件,你可以调用并且传入null。这样会返回你的应用在外部存储上私有路径的根目录。

  要记住在一个卸载应用时系统要删除的目录中创建目录如果你的文件在应用删除之后仍需要保存,你应该使用

  无论使用哪种方法,比较重要的一点是,要使用API提供的路径名常量,如等。这些路径名保证了文件会被系统正确处理。

  如果你提前知道你要存储多少数据,你可以实现查询是否有足够的存储空间,而不必引起一个 

  通过调用  和方法,你可以获取当前的空闲空间和当前卷的总空间。

  但是,系统并不保证你可以存储的容量和 方法获取的字节数一样多,如果该方法返回的容量要比你实际存储的数据大小多几个MB,或者存储后系统的填满程度小于90%,那么很可能是可以安全处理的。否则,可能就存储不下了。

  并没有要求你必须先检查剩余空间再存储文件,你可以把存储的语句写入一个try块中,然后catch IOException。在你并不知道文件多大时你需要这么做。

  在你不需要文件时你需要将其删除。

  最直接的方法:获得文件引用然后调用 方法:

  如果文件存储在内部存储上,可以请求  去定位和删除文件,通过调用:

  注意,当用户卸载你的app时,Android系统删除如下:

  所有在内部存储上的文件。

  所有用存储在外部存储上的文件。

  然而,你需要定期手工地删除利用 创建的所有缓存文件。并且,需要定期清除所有不再需要的文件。

  对于重复或结构性强的数据来说,把它们存储在数据库中是一种理想的做法。

  Android系统上你将会用到的数据库相关的APIs都在这个包中: 。

  SQL数据库中的主要原则之一就是构架(schema):一个关于这个数据库是如何组织的一个正式的声明。

  构架反映在创建SQL数据库的语句中。

  你可能会发现创建一个同伴类(companion class)很有用,同伴类同时被称作合约类(contract class),其中明确规定了你的构架的布局,以一种系统且自说明的方式。

  一个合约类(contract class)是一个常量的容器,这些常量定义了URI,表的名字,列的名字。

  合约类允许你在同一个包的其他类中使用这些名字常量。

  这就允许了你在一个地方改变列名,而同时把它传播到代码的其他地方去。

  组织合约类的一个好方法是:把对于你的整个数据库来说是全局的那些定义放在类的根级上;然后对于每一个表(table)创建一个内部类,列举其列。

  注意:通过实现 接口,你的内部类要继承一个基本的关键字域叫做_ID,一些Android的类比如cursor

  它不是必须的,但是它能帮助你的数据库更和谐地和Android framework工作

  比如,下面这个代码段定义了表名和列名:

  为了阻止不小心实例化合约类,给它一个私有的空构造函数

  一旦你定义好了你的数据库看起来什么样,你就应该实现一些方法,用来创建和维护数据库和数据表。

  下面是创建和删除表的一些典型的语句:

  就好像你存储文件到设备的内部存储上一样,Android在你应用相关的私有磁盘空间上存储你的数据库数据。

  你的数据是安全的,因为默认情况下其他应用无法访问这块区域。

   类提供了一些有用的APIs,当你使用这个类来获取数据库的引用时,系统仅在需要时执行可能长时间的操作:创建和更新数据库,而不是在应用启动的时候执行。

  你需要做的仅仅是调用 或方法。

  注意:因为它们是长时间运行的,所以请确保你在背景线程中调用:

  为了使用,创建一个子类,然后覆盖这三个回调函数:

  你也可以实现 ,但这并不是必须的。

  如下是一个对的实现例子:

  要访问你的数据库,需要先实例化你的的子类。

  想数据库中插入数据:通过向  方法中传入一个 对象实现。

  方法接收的第一个参数是表的名字,第二个参数是列的名字:可以通过这个参数设定一个列名,如果是空值,这样这个列就会插入NULL;如果第二个参数是null,那么如果是空值将不会被插入表中。

  从数据库中读取,是通过方法,向它传递你的选择标准和你想要的列。

  这个方法结合了和方法的元素,只不过它的列的表指定了你想取出的数据,而不是插入的数据。

  查询结果是由一个对象的形式返回给你的。

  想要看一个Cursor中的一行,可以使用  类中的各种move方法中的一个,当你开始读取值时你必须先调用它。

  一般情况下,你应该先调用方法,它将把读取位置指向结果中的第一项。

  对每一行,你可以通过Cursor类的get方法读取各列内容,比如 或 。

  对每一个get方法,你必须指定你想要的列的索引,你可以通过  或 方法来获得索引。

  要删除表中的行,你需要提供一定的选择标准来确定要删除的行。

  数据库API提供了一种机制,用于创建选择标准,防止SQL注入。

  该机制将选择标准分为一个选择从句和一些选择参数。这个从句定义了需要查看的列,也允许你结合列的测试。这些参数是和从句绑定的需要测试的值。

  因为结果不像常规的SQL语句那样处理,所以它是防SQL注入的。

  当你需要修改你的数据库中值的一个子集时,运用方法。

  更新数据表结合了方法中的content

}

我要回帖

更多关于 java中的long型变量 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信