利用したパッケージ
- connect-flash@0.1.1
- cookie-parser@1.4.6
- debug@2.6.9
- ejs@3.1.10
- express-session@1.18.0
- express@4.19.2
- http-errors@1.6.3
- morgan@1.9.1
- mysql2@3.9.4
- sequelize@6.37.3
Express Validator で作成します。
npm install mysql2
npm install sequelize
npm install -g sequelize-cli
npm install connect-flash
npm install express-session
次のコマンドを実行し、sequelize の初期化をします。
sequelize init
config/config.json が作成されるため、データベースの設定を自身の環境に合わせて編集します。
設定が終われば、次のコマンドを実行しデータベースを作成します。
sequelize db:create
データベースが作成されたら、次のコマンドを実行しモデルを作成します。
sequelize model:create --underscored --name user --attributes "name:string,age:integer"
models ディレクトリの中に、user.js というファイルが作成されます。
最後に次のコマンドを実行しマイグレーションをします。
sequelize db:migrate
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
const session = require("express-session"); // +
const flash = require('connect-flash'); // +
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var crudRouter = require('./routes/crud'); // +
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// +
app.use(session({
secret: "secret",
resave: false,
saveUninitialized: true,
}));
app.use(flash()); // +
// +
app.use((req, res, next) => {
res.locals.messages = req.flash();
next();
});
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/crud', crudRouter); // +
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
var express = require('express');
var router = express.Router();
var db = require('../models/');
router.get('/', function(req, res, next) {
db.user.count().then((total) => {
db.user.findAll().then(users => {
res.render('crud/index', { users: users, count: total});
});
});
});
router.get('/add', function(req, res, next) {
res.render('crud/add');
});
router.post('/add', function(req, res, next) {
console.log(req.body)
db.user.create({
name: req.body.name,
age: req.body.age
}).then((result) => {
console.log(result);
req.flash("success", "登録されました。");
res.redirect('/crud');
});
});
router.get('/show/:id', function(req, res, next) {
db.user.findOne({
where: { id: req.params.id }
}).then((user) => {
console.log(user);
res.render('crud/show', { user: user });
});
});
router.get('/edit/:id', function(req, res, next) {
db.user.findOne({
where: { id: req.params.id }
}).then((user) => {
console.log(user);
res.render('crud/edit', { user: user });
});
});
router.post('/edit', function(req, res, next) {
db.user.update(
{ name: req.body.name, age: req.body.age},
{ where: { id: req.body.id} }
).then((result) => {
console.log(result);
req.flash("success", "更新されました。");
res.redirect(`/crud/edit/${req.body.id}`);
});
});
router.get('/delete/:id', function(req, res, next) {
db.user.findOne({
where: { id: req.params.id }
}).then((user) => {
console.log(user);
res.render('crud/delete', { user: user });
});
});
router.post('/delete', function(req, res, next) {
db.user.destroy(
{ where: { id: req.body.id }}
).then(() => {
req.flash("success", "削除されました。");
res.redirect('/crud');
});
});
module.exports = router;
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div>
<h1>CRUD Add</h1>
<p><a href="/crud">ページトップへ</a></p>
<form method="POST" action="/crud/add" >
<p><label for="name_form">名前:<input type="text" name="name" id="name_form"></label></p>
<p><label for="age_form">年齢:<input type="number" name="age"></label></p>
<p><input type="submit" value=" 送信 " /></p>
</form>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div>
<h1>CRUD - Delete</h1>
<p><a href="/crud">ページトップへ</a></p>
<table>
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
</tr>
<tr>
<td><%= user["id"] %></td>
<td><%= user["name"] %></td>
<td><%= user["age"] %></td>
</tr>
</table>
<form method="POST" action="/crud/delete" >
<input type="hidden" name="id" value="<%= user['id'] %>">
<p><input type="submit" value="削除" /></p>
</form>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div>
<h1>CRUD - Edit</h1>
<p><a href="/crud">ページトップへ</a></p>
<% if(messages.success){ %>
<p><%= messages.success %></p>
<% } %>
<form method="POST" action="/crud/edit" >
<input type="hidden" name="id" value="<%= user['id'] %>">
<p>ID:<%= user['id'] %></p>
<p><label for="name_form">名前:<input type="text" name="name" id="name_form" value="<%= user['name'] %>"></label></p>
<p><label for="age_form">年齢:<input type="number" name="age" value="<%= user['age'] %>"></label></p>
<p><input type="submit" value=" 送信 " /></p>
</form>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/stylesheets/style.css">
<title>Document</title>
</head>
<body>
<div>
<h1>CRUD index</h1>
<p><a href="/crud/add">新規追加</a></p>
<% if(messages.success){ %>
<p><%= messages.success %></p>
<% } %>
<p>レコード総数:<%= count %></p>
<table>
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
<th>action</th>
</tr>
<% for(let i of users){ %>
<tr>
<td><%= i["id"] %></td>
<td><a href="/crud/show/<%= i['id'] %>"><%= i["name"] %></a></td>
<td><%= i["age"] %></td>
<td><a href="/crud/edit/<%= i['id'] %>">変更</a><a href="/crud/delete/<%= i['id'] %>">削除</a></td>
</tr>
<% } %>
</table>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>CRUD - Show</title>
</head>
<body>
<div>
<h1>CRUD - Show</h1>
<p><a href="/crud">ページトップへ</a></p>
<table>
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
</tr>
<tr>
<td><%= user["id"] %></td>
<td><%= user["name"] %></td>
<td><%= user["age"] %></td>
</tr>
</table>
<p><a href="/crud/edit/<%= user['id'] %>">変更ページへ</a></p>
<p><a href="/crud/delete/<%= user['id'] %>">削除ページへ</a></p>
</div>
</body>
</html>
楽観的ロックを追加し動作するかの確認です。
まずモデルに「 version: true 」を追加します。
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class user extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
}
}
user.init({
name: DataTypes.STRING,
age: DataTypes.INTEGER
}, {
sequelize,
modelName: 'user',
underscored: true,
version: true, // +
});
return user;
};
一度、データベースの usersテーブルを削除し、sequelize db:migrate を再度実行します。
とりあえずテーブルにデータを1つ投入します。
マイグレーションが終わった後、「 routes/crud.js 」の edit に関するルーティングを次のように変更します。
router.get('/edit/:id', function(req, res, next) {
try {
db.user.findOne({
where: { id: req.params.id }
}).then((user) => {
res.render('crud/edit', { user: user });
}).catch((error => {
req.flash("errors", "問題が発生しました。");
res.redirect('/crud');
}));
} catch(error) {
req.flash("errors", "問題が発生しました。");
res.redirect('/crud');
}
});
router.post('/edit', function(req, res, next) {
(async function(){
let transaction = await db.sequelize.transaction();
try {
const user = await db.user.findOne({
where: {
id: req.body.id
}
}, { transaction: transaction });
console.log(`age: ${user.age} version: ${user.version}`);
user.name = req.body.name;
user.age = req.body.age;
user.dataValues.version = req.body.version;
await user.save({ transaction: transaction });
transaction.commit();
transaction = null;
req.flash("success", "更新されました。");
res.redirect(`/crud/edit/${req.body.id}`);
} catch(error) {
console.log(error); // ※ OptimisticLockError [SequelizeOptimisticLockError]: Attempting to update a stale model instance: user
if (transaction) {
transaction.rollback();
}
req.flash("errors", "更新できませんでした。");
res.redirect('/crud');
}
})();
});
「 views/crud/edit.ejs 」を次のようにします。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div>
<h1>CRUD - Edit</h1>
<p><a href="/crud">ページトップへ</a></p>
<% if(messages.success){ %>
<p><%= messages.success %></p>
<% } %>
<form method="POST" action="/crud/edit" >
<input type="hidden" name="id" value="<%= user['id'] %>">
<!-- ↓ 変更箇所 -->
<input type="text" name="version" value="<%= user['version']%>" >
<p>ID:<%= user['id'] %></p>
<p><label for="name_form">名前:<input type="text" name="name" id="name_form" value="<%= user['name'] %>"></label></p>
<p><label for="age_form">年齢:<input type="number" name="age" value="<%= user['age'] %>"></label></p>
<p><input type="submit" value=" 送信 " /></p>
</form>
</div>
</body>
</html>
別々のウィンドウで、同じデータの変更のページを開くと「 name="version" 」のテキストエリアに同じ数字が入っています。
まず、1つのウィンドウの更新作業を行います。次に、もう一つのウィンドウも更新をしてみます。
2つ目のウィンドウで更新作業をするとコンソールに「 OptimisticLockError [SequelizeOptimisticLockError]: Attempting to update a stale model instance: user 」と表示されていれば、楽観的ロックは機能しています。