ActiveRecord预定义的事件,都在 yiidbBaseActiveRecord 中进行了明确:

 
abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
{
const EVENT_INIT = 'init'; // 初始化对象时触发
const EVENT_AFTER_FIND = 'afterFind'; // 执行查询结束时触发
const EVENT_BEFORE_INSERT = 'beforeInsert'; // 插入结束时触发
const EVENT_AFTER_INSERT = 'afterInsert'; // 插入之前触发
const EVENT_BEFORE_UPDATE = 'beforeUpdate'; // 更新记录前触发
const EVENT_AFTER_UPDATE = 'afterUpdate'; // 更新记录后触发
const EVENT_BEFORE_DELETE = 'beforeDelete'; // 删除记录前触发
const EVENT_AFTER_DELETE = 'afterDelete'; // 删除记录后触发
// ... ...
}

上述常量,定义了ActiveRecord对象常用的几个事件。这是预定义事件,我们可以直接拿来 用。事件的定义具体看事件(Event)部分的内容。

此外,作为ActiveRecord类的祖宗, yiibaseModel 类也定义了2个事件:

 
class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayable
{
const EVENT_BEFORE_VALIDATE = 'beforeValidate'; // 在验证之前触发
const EVENT_AFTER_VALIDATE = 'afterValidate'; // 在验证之后触发
// ... ...
}

因此,上述总共10个事件,可供开发者在写入业务流程时使用。

从上述事件来看,可以看出大部分事件是分别以before和after打头的成对事件。 有些是“读”操作时才会触发的事件,有些是“写”操作时发生的事件。

而且,“写”与“写”之间也是相互区别的。比如,增、改、删3个写操作, 都各自有一对事件先后在不同场景下触发。但这3种“写”操作不会被同时触发。

初始化事件¶

首先,第一个事件,无可争议的,是 EVENT_INIT 。这是由 yii\base\Object 所决定的。该事件在 init() 方法中被触发。而我们在 属性(Property)中已经说过这个方法是最早调用的几个方法之一。具体代码:

 
public function init()
{
parent::init();
// 这里触发EVENT_INIT事件
$this->trigger(self::EVENT_INIT);
}

虽然这个事件触发得早,但是实际使用中,这个事件使用频率不高。 仅是因为有的代码不得不在初始化阶段执行,所以才提供了这个事件。 而且,这个事件由于所处阶段特殊,不像有的事件,可以有一定的替代性。

比如, EVENT_AFTER_VALIDATE 和 EVENT_BEFORE_UPDATE 尽管泾渭分明, 但是由于是相继触发,所以某些情况下可以在一定程度上互相替代。但是, 上述10个事件中,仅有 EVENT_INIT 是在初始化阶段触发。所以,其具有不可替代性。

EVENT_INIT 事件通常用于初始化一些东西,从模块化的角度, 可以简单看成是将当前类的 init() 方法的内容, 作为一个Event Handler单独划分为一个模块。

AfterFind事件¶

EVENT_AFTER_FIND 事件在完成查询后触发,注意该事件少有地没有对应的Before事件。

另外一个区别于其他事件的不同在于,该事件并非由 ActiveRecord 自身触发。 而是由 yii\db\ActiveQuery 触发。准确的触发时点,是在查询完成后, 向ActiveRecord填充字段全部内容后触发。

具体代码在 yii\db\ActiveQuery::populate()

 
// 该方法为ActiveQuery将查询到的内容 $rows 填充到ActiveReocrd中去的方法
public function populate($rows)
{
if (empty($rows)) {
return [];
}
$models = $this->createModels($rows);
if (!empty($this->join) && $this->indexBy === null) {
$models = $this->removeDuplicatedModels($models);
}
if (!empty($this->with)) {
$this->findWith($this->with, $models);
}
if (!$this->asArray) {
// 重点在这个foreach里面的afterFind(),
// afterFind()不干别的,就是专门调用
// $this->trigger(self::EVENT_AFTER_FIND) 来触发事件的。
foreach ($models as $model) {
$model->afterFind();
}
}
return $models;
}

上面的代码我们可以看出,在完成查询之后,查询到了多少个记录, 就会触发多少次实例级别的 EVENT_AFTER_FIND 事件。 事件的级别,请看 事件(Event)部分的内容。

EVENT_AFTER_FIND 事件,用于查询后一些内容的填充。比如,有一个ActiveRecord, 专门用于表示博客文章,那么通常他有一个字段,用于表示发布的确切时间。

假设客户希望在前台显示文章时,不直接显示确切时间,而是显示如“3分钟前” “2个月前”之类的相对时间。那么,我们就需要有一个将绝对时间转化成相对时间的过程。

那么,就可以把这个转换过程的代码,写在 EVENT_AFTER_FIND 事件的Event Handler里。

验证事件¶

验证事件是在验证时先后触发的2个事件,这2个事件均由 yii\base\Model::validate 触发:

 
public function validate($attributeNames = null, $clearErrors = true)
{
if ($clearErrors) {
$this->clearErrors();
}
// 这里的 beforeValidate() 会调用
// $this->trigger(self::EVENT_BEFORE_VALIDATE, $event)
// 来触发 EVENT_BEFORE_VALIDATE 事件。
if (!$this->beforeValidate()) {
return false;
}
// 下面是后续的验证代码,这里不用过多关注
$scenarios = $this->scenarios();
$scenario = $this->getScenario();
if (!isset($scenarios[$scenario])) {
throw new InvalidParamException("Unknown scenario: $scenario");
}
if ($attributeNames === null) {
$attributeNames = $this->activeAttributes();
}
foreach ($this->getActiveValidators() as $validator) {
$validator->validateAttributes($this, $attributeNames);
}
// 这里的 afterValidate() 会调用
// $this->trigger(self::EVENT_AFTER_VALIDATE)
// 来触发 EVENT_AFTER_VALIDATE 事件。
$this->afterValidate();
return !$this->hasErrors();
}

这两个事件正如其名称所表示的,触发顺序为先 EVENT_BEFORE_VALIDATE 后 EVENT_AFTER_VALIDATE 。

这两个事件中, EVENT_BEFORE_VALIDATE 常用于验证前的一些规范化处理。 仍以博客文章的发布时间字段为例,在接收用户输入时, 我们的应用接收一个字符类似“2015年3月8日”之类的字符串。

但是数据库中我们一般并不以字符串形式保存时间,而是使用一个整型字段来保存。 这主要涉及存储空间,日期比较和排序,检索效率等数据库优化问题,具体不展开。 反正我们就是想把时间以整型形式进行保存。

那么,在验证用户输入之前,我们就需要将字符串类型的日期时间, 转换成整型类型的日期时间。否则的话,验证就通不过。

这个转换过程,就可以写在 EVENT_BEFORE_VALIDATE 的 Event Handler里面。

EVENT_BEFORE_VALIDATE 还有一个比较吸引人的特性, 它的Event Handler可以返回一个 boolean 值,当为 false 时, 表示后续的验证没必要进行了:

 
public function beforeValidate()
{
// 创建一个 ModelEvent,并交给 trigger 在触发事件时使用
$event = new ModelEvent;
$this->trigger(self::EVENT_BEFORE_VALIDATE, $event);
return $event->isValid;
}

上面的代码中, trigger() 将传入的第二个 $event 传递给 Event Handler, 使得相关的这些个 Event Handler 可以在必要时修改 $event->isValid 的值。 以此来决定是否可以取消后续的验证,直接视为验证没有通过。

EVENT_AFTER_VALIDATE 通常用于用户输入验证后的一些处理。比如, 用于写入操作前的一些通用处理。因为后头接下来的事件, 会分成插入、更新等独立事件。如果有一些写入前的通用处理,放在 EVENT_AFTER_VALIDATE 阶段是比较合适的。

至于验证通过与否,与 EVENT_AFTER_VALIDATE 事件没有关系,只要执行完所有验证了, 这个事件就会被触发。而且,该事件的Event Handler没有返回值,无法干预验证结果。

“写”事件¶

“写”事件是指插入、更新、删除等写入操作时触发的事件。一般情况下, 验证事件先于“写”事件被触发。

但这不是绝对的。Yii允许在执行“写”操作时,不调用 validate() 进行验证, 也就不触发验证事件。

下面,我们以更新操作update为例,来分析“写”事件。

首先,来看看 yii\db\BaseActiveRecord 里的有关代码:

 
public function save($runValidation = true, $attributeNames = null)
{
// insert() 和 update() 具体实现由ActiveRecord定义
if ($this->getIsNewRecord()) {
return $this->insert($runValidation, $attributeNames);
} else {
return $this->update($runValidation, $attributeNames) !== false;
}
}
// updateInternal() 由 update() 调用,
// 类似的有deleteInternal() ,由ActiveRecord定义,这里略去。
protected function updateInternal($attributes = null)
{
// beforeSave() 会触发相应的before事件
// 而且如果beforeSave()返回false,就可以中止更新过程。
if (!$this->beforeSave(false)) {
return false;
}
$values = $this->getDirtyAttributes($attributes);
// 没有字段有修改,那么实际上是不需要更新的。
// 因此,直接调用afterSave()来触发相应的after事件。
if (empty($values)) {
$this->afterSave(false, $values);
return 0;
}
// 以下为实际更新操作,不必细究。
$condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock();
if ($lock !== null) {
$values[$lock] = $this->$lock + 1;
$condition[$lock] = $this->$lock;
}
$rows = $this->updateAll($values, $condition);
if ($lock !== null && !$rows) {
throw new StaleObjectException('The object being updated is outdated.');
}
$changedAttributes = [];
foreach ($values as $name => $value) {
$changedAttributes[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
$this->_oldAttributes[$name] = $value;
}
// 重点看这里,触发了事件
$this->afterSave(false, $changedAttributes);
return $rows;
}
// 下面的beforeSave()和afterSave() 会根据判断是更新操作还是插入操作,
// 以此来决定是触发 INSERT 事件还是 UPDATE 事件。
public function beforeSave($insert)
{
$event = new ModelEvent;
// $insert 为 true 时,表示当前是插入操作,是个新记录,要触发INSERT事件
// $insert 为 false时,表示当前是插入操作,是个新记录,要触发INSERT事件
$this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event);
return $event->isValid;
}
public function afterSave($insert, $changedAttributes)
{
// $insert 为 true 时,表示当前是插入操作,是个新记录,要触发INSERT事件
// $insert 为 false时,表示当前是插入操作,是个新记录,要触发INSERT事件
$this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE, new AfterSaveEvent([
'changedAttributes' => $changedAttributes
]));
}

就“写”操作而言,表面上调用的是 ActiveRecord 的 update() insert() delete() 等方法。

但是,更新最终调用的是 BaseActiveRecord::updateInteranl() , 插入最终调用的是 ActiveRecord::insertInternal() , 而删除最终调用的是 ActiveRecord::deleteInternal() 。

这些 internal 方法,会触发相应的“写”事件,但不会调用验证方法, 也不会触发验证事件。验证方法 validation() 由 update() insert() 调用。 因此,验证事件也由这两个方法触发。

而且,这些 update() insert() 可以选择不进行验证,在压根不触发验证事件的情况下,就可以完成“写”操作。

因此,虽然 EVENT_AFTER_VALIDATE 和 EVENT_BEFORE_UPDATE 相继发生, 在使用上有时可以有一定程度的替代。但是,其实两者是有严格界限的。 原因就是验证事件可能在“写”操作过程中不被触发。

此外,删除过程不触发验证事件。都要删掉的东西了,还需要验证么?

对于 internal 方法们,只是触发了相应的before和after“写”事件。

其中,before事件的Event Handler可以通过将 $event->isValid 设为 false 来中止“写”操作。

与在验证事件阶段中止时,视为验证没通过不同,这里的中止视为“写”操作失败。

与验证事件阶段类似,after事件时由于生米已成熟饭,再也无法干预“写”操作的结果。

响应事件¶

前面提到的诸多预定义事件,为我们开发提供了方便。基本上使用这些预定义事件, 就可以满足各种开发需求了。

但是凡事总有例外,特别是对于业务逻辑复杂的情况。 比如,默认的删除事件,会在确确实实地要从数据表中删除记录时触发。 但是,有的时候,我们并非真的想从数据表中删除记录,我们可能使用一个类似于“状态” 的字段,在想要删除时,只是将记录的“状态”标记为“删除”。

这种需求并不少见。这样便于我们在后悔时,可以“恢复”删除。

从实质上是看,这其实是一个更新操作。那么预定义的 EVENT_BEFORE_DELETE 和 EVENT_AFTER_DELETE 就不适用了。

对此,我们可以自己定义事件来使用。具体的方法可以参见 事件(Event)部分的内容。

大致的代码可以是这样的:

 
class Post extends \yii\db\ActiveRecord {
// 第一步:定义自己的事件
const EVENT_BEFORE_MARK_DELETE = 'beforeMarkDelete';
const EVENT_AFTER_MARK_DELETE = 'afterMarkDelete';
// 第二步:定义Event Handler
public function onBeforeMarkDelete () {
// ... do sth ...
}
// 第三步:在初始化阶段绑定事件和Event Handler
public function init()
{
parent::init();
$this->trigger(self::EVENT_INIT);
// 完成绑定
$this->on(self::EVENT_BEFORE_MARK_DELETE, [$this, 'onBeforeMarkDelete']);
}
// 第四步:触发事件
public function beforeSave($insert) {
// 注意,重载之后要调用父类同名函数
if (parent::beforeSave($insert)) {
$status = $this->getDirtyAttributes(['status']);
// 这个判断意会即可
if (!empty($status) && $status['status'] == self::STATUS_DELETE) {
// 触发事件
$this->trigger(self::EVENT_BEFORE_MARK_DELETE);
}
return true;
} else {
return false;
}
}
}

上面的代码理解个大致流程就OK了,不用细究。

在事件的响应上,我们有2个方法来写入我们的代码。

最直观的方式,是使用 事件(Event)中介绍的 Event Handler。也就是上面代码展现的, 为类定义一个成员函数,作为Event Handler。同时,在类的构造函数或初始化方法中, 把事件和Event Handler绑定起来。最后,在合适的时候,触发事件即可。

另一种方式,是直接重载上面多次提到的各种 beforeSave() afterSave() beforeValidate() afterValidate() 等方法。比如,上面的例子可以改成:

 
class Post extends \yii\db\ActiveRecord {
// 不需要定义自己的事件
//const EVENT_BEFORE_MARK_DELETE = 'beforeMarkDelete';
//const EVENT_AFTER_MARK_DELETE = 'afterMarkDelete';
// 不需要定义Event Handler
//public function onBeforeMarkDelete () {
// ... do sth ...
//}
// 不需要绑定事件和Event Handler
//public function init()
//{
// parent::init();
// $this->trigger(self::EVENT_INIT);
// $this->on(self::EVENT_BEFORE_MARK_DELETE, [$this, 'onBeforeMarkDelete']);
//}
// 只需要重载
public function beforeSave($insert) {
// 注意,重载之后要调用父类同名函数
if (parent::beforeSave($insert)) {
$status = $this->getDirtyAttributes(['status']);
// 这个判断意会即可
if (!empty($status) && $status['status'] == self::STATUS_DELETE) {
// 不需要触发事件
//$this->trigger(self::EVENT_BEFORE_MARK_DELETE);
// 但是需要把原来 Event Handler的内容放到这里来
// ... do sth ...
}
return true;
} else {
return false;
}
}
}

对比来看,重载 beforeSave() 的方式要简洁很多。但是这种方式从严格意义上来讲, 并不是正规的事件处理机制。只不过是利用了Yii已经预先定义好的函数调用流程。 在使用中,需要格外注意的是,一定要在重载的函数中,调用父类的同名函数。否则的话, trigger() 不再被自动调用,相关事件就不会再被触发。整个类的事件机制, 就全被破坏了。

关联操作¶

在实际开发中,有一种典型的场景,即对数据库某个表的某个记录进行修改时,需要对关联 的表中的相关记录做相应的修改。

比如,一个典型的博客,表示文章的数据表中有一个字段用于记录当前文章有多少条评论。 那么,当用户发表新评论时,另一个用于表示评论的表中,理所当然地要插入一条新记录。 不可避免的,文章表中,被评论文章所对应的记录,其评论计数字段应当加1。

那么这一过程怎么编程实现呢?

最直白的方法,是在操作评论记录的代码之前(后),写入相应的增加文章评论计数的代码 。 这样好理解,但是不同功能代码的界限不清晰。

另一种方法,是借助事件(Event),将增加文章评论计数的代码,写到 评论ActiveReocrd的相应Event Handler中。比如, EVENT_AFTER_INSERT 。

这样子代码功能界限清晰,便于查找、修改和扩展。 缺点是可能需要多看几个方法才能了解整个业务流程。实际中我们多采用这种方法。

在实现数据库记录的关联操作时,第一步就是要利用上述的各种事件,来产生关联性。 其次,是要把这些关联性绑死在一起。也就是用数据库的事务。具体的原理, 参考 事务部分的内容。

下面,我们以上面提到的博客文章新增一个评论为例,讲解如何实现关联操作。

声明需要事务支持的操作¶

在ActiveRecord中有一个方法,用于告诉Yii我们的哪些操作需要事务支持。对于插入、 更新、删除的1个或多个操作需要事务支持时,可以在这个方法中进行声明。 这个方法就是 ActiveRecord::transactions()

 
class ActiveRecord extends BaseActiveRecord
{
// 定义插入、更新、删除操作,及表示3合1的ALL
const OP_INSERT = 0x01;
const OP_UPDATE = 0x02;
const OP_DELETE = 0x04;
const OP_ALL = 0x07;
// 需要事务支持时,重载这个方法。
public function transactions()
{
return [];
}
// ... ...
}

默认情况下,这个 transactions() 返回一个空数组,表示不需要任何的事务支持。

我们的博客文章增加评论的案例中是要用到的,那么,我们可以在评论模型 Comment 中,作如下声明:

 
public function transactions() {
return [
'addComment' => self::OP_INSERT,
];
}

这个方法所返回的数组中,元素的键,如上面的 addComment 表示场景(scenario), 元素值,表示的是操作的类型,即 OP_INSERT 等。

ActiveRecord定义了3种可能会用到事务支持的操作 OP_INSERT OP_UPDATE OP_DELETE 分别表示插入、更新、删除。

可以把这3个操作两两组合作为 transactions() 所返回数组元素的值。 如, self::OP_INSERT|self::OP_UPDATE `` 表示插入和更新操作。 也可以直接使用 ``OP_ALL 表示3种操作都包含。

启用事务¶

上一步中的 transactions() 被 ActiveRecord::isTransactional() 所调用:

 
// $operation就是预定义的OP_INSERT 等3种单一操作类型
public function isTransactional($operation)
{
// 获取当前的scenario
$scenario = $this->getScenario();
$transactions = $this->transactions();
return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation);
}

这个 isTransactional() 就是判断当前场景下,当前操作类型,是否已经在 transactions() 中声明为需要事务支持。

而这个 isTransactional() 又被各种“写”操作方法所调用。 在我们的博客文章新增评论的案例中,就是被 insert() 所调用:

 
public function insert($runValidation = true, $attributes = null)
{
if ($runValidation && !$this->validate($attributes)) {
Yii::info('Model not inserted due to validation error.', __METHOD__);
return false;
}
// 这里调用了 isTransactional(),判断当前场景下,
// 插入操作是否需要事务支持
if (!$this->isTransactional(self::OP_INSERT)) {
// 无需事务支持,那就直接insert了事
return $this->insertInternal($attributes);
}
// 以下是需要事务支持的情况,那就启用事务
$transaction = static::getDb()->beginTransaction();
try {
$result = $this->insertInternal($attributes);
if ($result === false) {
$transaction->rollBack();
} else {
$transaction->commit();
}
return $result;
} catch (\Exception $e) {
$transaction->rollBack();
throw $e;
}
}

很明显的,我们只需要在 transactions() 中声明需要事务支持的操作就足够了。 后续的怎么使声明生效的,Yii框架已经替我们写好了。

在上面 insert() 的代码中,通过我们的声明,Yii发现需要事务支持, 于是就调用了 static::getDb()->beginTransaction() 来启用事务。 事务的原理,请看 事务部分的内容。

在事件响应中写入关联操作¶

接下来,我们在关联的事件,如 EVENT_AFTER_INSERT 中,写入关联操作。 这里,我们就是要更新博客文章模型 Post 的评论计数字段。

因此,可以在评论模型 Comment 完成插入后的 EVENT_AFTER_INSERT 阶段, 写入更新 Post::comment_counter 的代码。如果使用简洁形式的事件响应方式, 那么代码可以是:

 
class Comment extends \yii\db\ActiveRecord {
// 通过重载afterSave来“响应”事件
public function afterSave($insert) {
if (parent::beforeSave($insert)) {
// 新增一个评论
if ($insert) {
// 关联Post的操作,评论计数字段+1
$post = Post::find($this->postId);
$post->comment_counter += 1;
$post->save(false);
}
}
}
}

回顾下实现关联操作的过程,其实就2步:

  • 先是在 transactions() 中声明要事务支持的操作类型,比如上面的例子, 声明的是插入操作。
  • 在合适事件响应函数中,写下关联操作代码。

AcitveReocrd事件和关联操作的更多相关文章

  1. Nginx事件处理中的connection和read、write事件的关联

    /*********************************************************************  * Author  : Samson  * Date   ...

  2. jquery css事件编程 位置 操作

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  3. thinkphp框架中“关联操作”的完整定义详解

    在复杂的关联操作中,如果要给关联定义增加可选的属性,我们可以采用完整定义的方式. 完整定义的格式是: protected $_link = array(     '关联表名1'  =>  arr ...

  4. android事件系列-onTouch事件与手势操作

    提示记忆:应用流程:在Activity中对控件执行 view.setOnTouchListener( OnTouchListener i);实现里面的OnTouchListener 接口中的方法,重点 ...

  5. loadrunner:关联操作

    文章以实例讲解loadrunner中的关联操作,内容包括:自动关联.手动关联和关联规则的设置. 1.1.1     准备工作 在web tours项目默认设置里,登录操作是没有生成sessionID的 ...

  6. 第一百七十一节,jQuery,高级事件,模拟操作,命名空间,事件委托,on、off 和 one

    jQuery,高级事件,模拟操作,命名空间,事件委托,on.off 和 one 学习要点: 1.模拟操作 2.命名空间 3.事件委托 4.on.off 和 one jQuery 不但封装了大量常用的事 ...

  7. jmeter之关联操作

    测试接口过程中,常常会遇到这样的一个情况:上一个请求返回的数据,另外一个接口需要要使用.那么,使用Jmeter操作时我们常常可以用“关联”来实现. 以接口“登录”和“金币充值”为例:即在做“金币充值” ...

  8. FlowNet:simple / correlation 与 相关联操作

    Flow Net : simple / correlation 与 相关联操作 ​ 上一篇文章中(还没来得及写),已经简单的讲解了光流是什么以及光流是如何求得的.同时介绍了几个光流领域的经典传统算法. ...

  9. CocoStudio基础教程(6)使用CocoStudio编辑帧事件并关联到程序

    1.概述 帧事件也是新加入的功能.这篇中我们将看到如何使用它.我们将上篇中制作的动画稍加修改. 2.用途与原理 首先介绍一下帧事件.正如其名:一个与帧相关联的事件. 为什么要这么做呢?首先没人想做一大 ...

随机推荐

  1. H2数据库启动提示8082端口被占用

    The Web Console server could not be started. Possible cause: another server is already running at ht ...

  2. SSH环境搭建之Spring环境搭建篇

    SSH环境搭建之Spring环境搭建篇 一.引入Spring所使用的JAR文件 二.在src目录下创建beans.xml(Spring的容器文件) <?xml version="1.0 ...

  3. MyBatis-05-解决属性名和字段名不一致的问题

    5.解决属性名和字段名不一致的问题 1.问题 数据库中的字段 新建一个项目,拷贝之前的,测试实体类字段不一致的情况. public class User { private int id; priva ...

  4. tar命令--数据解档(三)解压.tar.gz文件报错 gzip:stdin:not in gzip format

    毕竟是生产..... 提示以下信息:  gzip: stdin: not in gzip format  tar: Child returned status 1  tar: Error is not ...

  5. Linux环境下安装MySQL5.7

    记录一下Linux环境下安装MySQL,大家按顺序执行即可,5分钟内即可完成安装,亲测可行.不过下载MySQL安装包需要大家花费一些功夫,送个链接给大家,大家按需下载: https://dev.mys ...

  6. 自己编写一个Java监听器

    Java监听器 1.原理: 当范围对象的状态发生变化的时候,服务器自动调用监听器对象中的方法. 例如:创建一个“人”类Class Person 人拥有吃的方法public void eat(){},我 ...

  7. WebApiHelper

    /// <summary> /// 根据token过滤 /// </summary> /// <param name="list"></p ...

  8. Java对象间的关系

    1 综述 在Java中对象与对象的关系总体分为四类,分别是:依赖.关联.聚合和组合. (1)依赖(Dependency)关系是类与类之间的联接.依赖关系表示一个类依赖于另一个类的定义,一般而言,依赖关 ...

  9. 最大的矩形(CCF)

    问题描述 在横轴上放了n个相邻的矩形,每个矩形的宽度是1,而第i(1 ≤ i ≤ n)个矩形的高度是hi.这n个矩形构成了一个直方图.例如,下图中六个矩形的高度就分别是3, 1, 6, 5, 2, 3 ...

  10. 【线性代数】3-3:秩(Rank)

    title: [线性代数]3-3:秩(Rank) categories: Mathematic Linear Algebra keywords: Rank Row Reduced form Pivot ...