QSqlDatabase的进一步封装(多线程支持+更加简单的操作)——同时支持MySQL, SQL Server和Sqlite

开发背景:

1.直接用QSqlDatabase我觉得太麻烦了;

2.对于某些数据库,多个线程同时使用一个QSqlDatabase的时候会崩溃;

3.这段时间没什么干货放出来觉得浑身不舒服,就想写一个。

于是,我就封装了一下

只要简单的实例化,然后通过query()就可以拿到QSqlQuery的实例化对象。

还自带计时,一段时间不用可自动关闭,既保证效率也不占用无用资源。

注:需要C++11的支持

不多说,上代码:

JasonQt_Database.h

[cpp] view plaincopy
 
  1. #ifndef __JasonQt_Database_h__  
  2. #define __JasonQt_Database_h__  
  3.   
  4. // C++ lib import  
  5. #include <functional>  
  6.   
  7. // Qt lib import  
  8. #include <QtCore>  
  9. #include <QtSql>  
  10.   
  11. #define PropertyDeclare(Type, Name, setName, ...)                     
  12.     private:                                                          
  13.     Type m_ ## Name __VA_ARGS__;                                      
  14.     public:                                                           
  15.     inline const Type &Name(void) const { return m_ ## Name; }        
  16.     inline void setName(const Type &Name) { m_ ## Name = Name; }      
  17.     private:  
  18.   
  19. namespace JasonQt_Database  
  20. {  
  21.   
  22. enum DatabaseModeEnum{ DatabaseNameMode, DatabaseHostMode };  
  23.   
  24. enum QueryMode { QueryAutoMode, QueryMultiMode, QuerySingleMode };  
  25.   
  26. class DatabaseSettings  
  27. {  
  28. private:  
  29.     PropertyDeclare(DatabaseModeEnum, databaseMode, setDatabaseMode)  
  30.   
  31.     PropertyDeclare(QString, databaseType, setDatabaseType)  
  32.     PropertyDeclare(QString, connectionName, setConnectionName)  
  33.   
  34.     // File mode  
  35.     PropertyDeclare(QString, nameModeName, setNameModeName)  
  36.   
  37.     // Host mode  
  38.     PropertyDeclare(QString, hostModeHostName, setHostModeHostName)  
  39.     PropertyDeclare(QString, hostModeDatabaseName, setHostModeDatabaseName)  
  40.     PropertyDeclare(QString, hostModeUserName, setHostModeUserName)  
  41.     PropertyDeclare(QString, hostModePassword, setHostModePassword)  
  42.   
  43. private:  
  44.     DatabaseSettings(const DatabaseModeEnum &databastMode, const QString &databaseType, const QString &connectionName);  
  45.   
  46. public:  
  47.     DatabaseSettings(const QString &databaseType, const QString &connectionName, const QString &nameModeName);  
  48.   
  49.     DatabaseSettings(const QString &databaseType, const QString &connectionName, const QString &hostModeHostName, const QString &hostModeDatabaseName, const QString &hostModeUserName, const QString &hostModePassword);  
  50. };  
  51.   
  52. class ConnectSettings  
  53. {  
  54. private:  
  55.     PropertyDeclare(int, maxOpenTime, setMaxOpenTime)  
  56.     PropertyDeclare(QueryMode, queryMode, setQueryMode)  
  57.     PropertyDeclare(int, minWaitTime, setMinWaitTime)  
  58.   
  59. public:  
  60.     ConnectSettings(const int &maxOpenTime = 60 * 1000, const QueryMode &queryMode = QueryAutoMode, const int &minWaitTime = -1);  
  61. };  
  62.   
  63. class Query  
  64. {  
  65. private:  
  66.     QSqlQuery *m_query;  
  67.     QMutex *m_mutex = NULL;  
  68.   
  69. public:  
  70.     Query(QSqlDatabase &dataBase, QMutex *mutex = NULL);  
  71.   
  72.     Query(Query &&other);  
  73.   
  74.     ~Query(void);  
  75.   
  76.     inline QSqlQuery *operator->(void) { return m_query; }  
  77.   
  78.     inline QSqlQuery *operator*(void) { return m_query; }  
  79.   
  80.     QSqlQuery *takeQuery(void);  
  81.   
  82.     QMutex *takeMutex(void);  
  83. };  
  84.   
  85. class ConnectNode: public QObject  
  86. {  
  87.      Q_OBJECT  
  88.   
  89. private:  
  90.     QSqlDatabase *m_database = NULL;  
  91.     QString m_connectionName;  
  92.   
  93.     DatabaseSettings m_dataBaseSettings;  
  94.     ConnectSettings m_connectSettings;  
  95.   
  96.     QTimer *m_autoClose = NULL;  
  97.     QMutex *m_mutex = NULL;  
  98.   
  99. public:  
  100.     ConnectNode(const DatabaseSettings &dataBaseSettings, const ConnectSettings &connectSettings);  
  101.   
  102.     ~ConnectNode(void);  
  103.   
  104. public:  
  105.     Query query(void);  
  106.   
  107. public slots:  
  108.     bool addDataBase(void);  
  109.   
  110.     void removeDataBase(void);  
  111.   
  112.     bool open(void);  
  113.   
  114.     void close(void);  
  115.   
  116. signals:  
  117.     void controlStartAutoClose(void);  
  118.   
  119.     void controlStopAutoClose(void);  
  120. };  
  121.   
  122. class Control  
  123. {  
  124. private:  
  125.     DatabaseSettings m_databaseSettings;  
  126.     ConnectSettings m_connectSettings;  
  127.   
  128.     QMap<qint64, ConnectNode *> m_node;  
  129.   
  130.     QMutex m_mutex;  
  131.     QTime *m_wait = NULL;  
  132.   
  133. public:  
  134.     Control(const DatabaseSettings &databaseSettings, const ConnectSettings &connectSettings = ConnectSettings());  
  135.   
  136.     Control(const Control &) = delete;  
  137.   
  138.     ~Control(void);  
  139.   
  140. public:  
  141.     void removeAll(void);  
  142.   
  143.     Query query(void);  
  144.   
  145. private:  
  146.     void insert(const qint64 &key);  
  147. };  
  148.   
  149. }  
  150.   
  151. #endif//__JasonQt_Database_h__  

JasonQt_Database.cpp

[cpp] view plaincopy
 
  1. #include "JasonQt_Database.h"  
  2.   
  3. using namespace JasonQt_Database;  
  4.   
  5. // DatabaseSettings  
  6. DatabaseSettings::DatabaseSettings(const DatabaseModeEnum &databastMode, const QString &databaseType, const QString &connectionName)  
  7. {  
  8.     m_databaseMode = databastMode;  
  9.     m_databaseType = databaseType;  
  10.     m_connectionName = connectionName;  
  11. }  
  12.   
  13. DatabaseSettings::DatabaseSettings(const QString &databaseType, const QString &connectionName, const QString &nameModeName):  
  14.     DatabaseSettings(DatabaseNameMode, databaseType, connectionName)  
  15. {  
  16.     m_nameModeName = nameModeName;  
  17. }  
  18.   
  19. DatabaseSettings::DatabaseSettings(const QString &databaseType, const QString &connectionName, const QString &hostModeHostName, const QString &hostModeDatabaseName, const QString &hostModeUserName, const QString &hostModePassword):  
  20.     DatabaseSettings(DatabaseHostMode, databaseType, connectionName)  
  21. {  
  22.     m_hostModeHostName = hostModeHostName;  
  23.     m_hostModeDatabaseName = hostModeDatabaseName;  
  24.     m_hostModeUserName = hostModeUserName;  
  25.     m_hostModePassword = hostModePassword;  
  26. }  
  27.   
  28. // ConnectSettings  
  29. ConnectSettings::ConnectSettings(const int &maxOpenTime, const QueryMode &queryMode, const int &minWaitTime)  
  30. {  
  31.     m_maxOpenTime = maxOpenTime;  
  32.     m_queryMode = queryMode;  
  33.     m_minWaitTime = minWaitTime;  
  34. }  
  35.   
  36. // Query  
  37. Query::Query(QSqlDatabase &dataBase, QMutex *mutex):  
  38.     m_query(new QSqlQuery(dataBase))  
  39. {  
  40.     m_mutex = mutex;  
  41. }  
  42.   
  43. Query::Query(Query &&other)  
  44. {  
  45.     m_query = other.takeQuery();  
  46.     m_mutex = other.takeMutex();  
  47. }  
  48.   
  49. Query::~Query(void)  
  50. {  
  51.     if(m_query)  
  52.     {  
  53.         delete m_query;  
  54.     }  
  55.     if(m_mutex)  
  56.     {  
  57.         m_mutex->unlock();  
  58.     }  
  59. }  
  60.   
  61. QSqlQuery *Query::takeQuery(void)  
  62. {  
  63.     auto buf = m_query;  
  64.     m_query = NULL;  
  65.     return buf;  
  66. }  
  67.   
  68. QMutex *Query::takeMutex(void)  
  69. {  
  70.     auto buf = m_mutex;  
  71.     m_mutex = NULL;  
  72.     return buf;  
  73. }  
  74.   
  75. // ConnectNode  
  76. ConnectNode::ConnectNode(const DatabaseSettings &dataBaseSettings, const ConnectSettings &connectSettings):  
  77.     m_dataBaseSettings(dataBaseSettings),  
  78.     m_connectSettings(connectSettings)  
  79. {  
  80.     m_connectionName = QString("%1(%2)").arg(m_dataBaseSettings.connectionName()).arg(QString::number(qint64(QThread::currentThread()), 16));  
  81.   
  82.     m_mutex = new QMutex(QMutex::Recursive);  
  83.   
  84.     if(m_connectSettings.maxOpenTime())  
  85.     {  
  86.         m_autoClose = new QTimer;  
  87.         m_autoClose->setSingleShot(true);  
  88.         m_autoClose->setInterval(m_connectSettings.maxOpenTime());  
  89.         m_autoClose->moveToThread(qApp->thread());  
  90.         m_autoClose->setParent(qApp);  
  91.   
  92.         connect(m_autoClose, SIGNAL(timeout()), this, SLOT(close()), Qt::DirectConnection);  
  93.         connect(this, SIGNAL(controlStartAutoClose()), m_autoClose, SLOT(start()));  
  94.         connect(this, SIGNAL(controlStopAutoClose()), m_autoClose, SLOT(stop()));  
  95.     }  
  96.   
  97.     this->addDataBase();  
  98. }  
  99.   
  100. ConnectNode::~ConnectNode(void)  
  101. {  
  102.     if(m_mutex){ m_mutex->lock(); }  
  103.   
  104.     if(m_autoClose)  
  105.     {  
  106.         m_autoClose->deleteLater();  
  107.     }  
  108.   
  109.     this->removeDataBase();  
  110.   
  111.     if(m_mutex){ m_mutex->unlock(); }  
  112.     if(m_mutex){ delete m_mutex; }  
  113. }  
  114.   
  115. Query ConnectNode::query(void)  
  116. {  
  117.     if(!m_database)  
  118.     {  
  119.         this->addDataBase();  
  120.     }  
  121.   
  122.     if(!m_database->isOpen())  
  123.     {  
  124.         m_database->open();  
  125.     }  
  126.   
  127.     if(m_mutex){ m_mutex->lock(); }  
  128.   
  129.     Query buf(*m_database, m_mutex);  
  130.     emit controlStartAutoClose();  
  131.     return buf;  
  132. }  
  133.   
  134. bool ConnectNode::addDataBase(void)  
  135. {  
  136.     if(m_mutex){ m_mutex->lock(); }  
  137.   
  138.     if(m_database)  
  139.     {  
  140.         this->removeDataBase();  
  141.     }  
  142.   
  143.     m_database = new QSqlDatabase(QSqlDatabase::addDatabase(m_dataBaseSettings.databaseType(), m_connectionName));  
  144.   
  145.     switch(m_dataBaseSettings.databaseMode())  
  146.     {  
  147.         case DatabaseNameMode:  
  148.         {  
  149.             m_database->setDatabaseName(m_dataBaseSettings.nameModeName());  
  150.             break;  
  151.         }  
  152.         case DatabaseHostMode:  
  153.         {  
  154.             m_database->setHostName(m_dataBaseSettings.hostModeHostName());  
  155.             m_database->setDatabaseName(m_dataBaseSettings.hostModeDatabaseName());  
  156.             m_database->setUserName(m_dataBaseSettings.hostModeUserName());  
  157.             m_database->setPassword(m_dataBaseSettings.hostModePassword());  
  158.             break;  
  159.         }  
  160.         default:  
  161.         {  
  162.             if(m_mutex){ m_mutex->unlock(); }  
  163.             return false;  
  164.         }  
  165.     }  
  166.   
  167.     const auto &&flag = this->open();  
  168.   
  169.     if(m_mutex){ m_mutex->unlock(); }  
  170.   
  171.     return flag;  
  172. }  
  173.   
  174. void ConnectNode::removeDataBase(void)  
  175. {  
  176.     if(m_mutex){ m_mutex->lock(); }  
  177.   
  178.     delete m_database;  
  179.     m_database = NULL;  
  180.     QSqlDatabase::removeDatabase(m_connectionName);  
  181.   
  182.     if(m_mutex){ m_mutex->unlock(); }  
  183. }  
  184.   
  185. bool ConnectNode::open(void)  
  186. {  
  187.     if(!m_database)  
  188.     {  
  189.         this->addDataBase();  
  190.     }  
  191.   
  192.     if(m_mutex){ m_mutex->lock(); }  
  193.   
  194.     emit controlStartAutoClose();  
  195.     const auto &&Flag = m_database->open();  
  196.   
  197.     if(m_mutex){ m_mutex->unlock(); }  
  198.   
  199.     return Flag;  
  200. }  
  201.   
  202. void ConnectNode::close(void)  
  203. {  
  204.     if(m_mutex)  
  205.     {  
  206.         if(m_mutex->tryLock())  
  207.         {  
  208.             m_mutex->unlock();  
  209.             emit controlStopAutoClose();  
  210.             m_database->close();  
  211.         }  
  212.         else  
  213.         {  
  214.             emit controlStartAutoClose();  
  215.         }  
  216.     }  
  217.     else  
  218.     {  
  219.         emit controlStopAutoClose();  
  220.         m_database->close();  
  221.     }  
  222. }  
  223.   
  224. // Control  
  225. Control::Control(const DatabaseSettings &databaseSettings, const ConnectSettings &connectSettings):  
  226.     m_databaseSettings(databaseSettings),  
  227.     m_connectSettings(connectSettings)  
  228. {  
  229.     if(m_connectSettings.queryMode() == QueryAutoMode)  
  230.     {  
  231.         if(databaseSettings.databaseType() == "QMYSQL")  
  232.         {  
  233.             m_connectSettings.setQueryMode(QueryMultiMode);  
  234.         }  
  235.         else if(databaseSettings.databaseType() == "QODBC")  
  236.         {  
  237.             m_connectSettings.setQueryMode(QueryMultiMode);  
  238.         }  
  239.         else  
  240.         {  
  241.             m_connectSettings.setQueryMode(QuerySingleMode);  
  242.         }  
  243.     }  
  244.     if(m_connectSettings.queryMode() == QuerySingleMode)  
  245.     {  
  246.         this->insert(qint64(QThread::currentThread()));  
  247.     }  
  248.   
  249.     if(m_connectSettings.minWaitTime() == -1)  
  250.     {  
  251.         if(databaseSettings.databaseType() == "QMYSQL")  
  252.         {  
  253.             m_connectSettings.setMinWaitTime(0);  
  254.         }  
  255.         else if(databaseSettings.databaseType() == "QODBC")  
  256.         {  
  257.             m_connectSettings.setMinWaitTime(0);  
  258.         }  
  259.         else  
  260.         {  
  261.             m_connectSettings.setMinWaitTime(5);  
  262.             m_wait = new QTime;  
  263.             m_wait->start();  
  264.         }  
  265.     }  
  266.     else  
  267.     {  
  268.         m_wait = new QTime;  
  269.         m_wait->start();  
  270.     }  
  271. }  
  272.   
  273. Control::~Control(void)  
  274. {  
  275.     for(auto &now: m_node)  
  276.     {  
  277.         now->deleteLater();  
  278.     }  
  279.     if(m_wait)  
  280.     {  
  281.         delete m_wait;  
  282.     }  
  283. }  
  284.   
  285. void Control::removeAll(void)  
  286. {  
  287.     m_mutex.lock();  
  288.   
  289.     for(auto &Now: m_node)  
  290.     {  
  291.         Now->removeDataBase();  
  292.     }  
  293.   
  294.     m_mutex.unlock();  
  295. }  
  296.   
  297. Query Control::query(void)  
  298. {  
  299.     if(m_wait)  
  300.     {  
  301.         const auto &&flag = m_connectSettings.minWaitTime() - m_wait->elapsed();  
  302.         m_wait->restart();  
  303.         if(flag > 0)  
  304.         {  
  305.             QThread::msleep(flag);  
  306.         }  
  307.     }  
  308.   
  309.     if(m_connectSettings.queryMode() == QueryMultiMode)  
  310.     {  
  311.         m_mutex.lock();  
  312.   
  313.         const auto &¤tThread = qint64(QThread::currentThread());  
  314.         const auto &&now = m_node.find(currentThread);  
  315.         if(now != m_node.end())  
  316.         {  
  317.             auto buf((*now)->query());  
  318.   
  319.             m_mutex.unlock();  
  320.             return buf;  
  321.         }  
  322.         else  
  323.         {  
  324.             this->insert(qint64(QThread::currentThread()));  
  325.   
  326.             m_mutex.unlock();  
  327.             return this->query();  
  328.         }  
  329.     }  
  330.     else  
  331.     {  
  332.         return (*m_node.begin())->query();  
  333.     }  
  334. }  
  335.   
  336. void Control::insert(const qint64 &key)  
  337. {  
  338.     m_node[key] = new ConnectNode(m_databaseSettings, m_connectSettings);  
  339. }  

使用:

[cpp] view plaincopy
 
  1. // Qt lib import  
  2. #include <QCoreApplication>  
  3. #include <QtConcurrent>  
  4. #include <QSqlError>  
  5.   
  6. // JasonQt lib import  
  7. #include "JasonQt/JasonQt_Database.h"  
  8.   
  9. int main(int argc, char *argv[])  
  10. {  
  11.     QCoreApplication a(argc, argv);  
  12.   
  13.     /* 
  14.      * 注:关于附加参数 
  15.      * 这是可以不写的,如果要写的话,可以参考这个: 
  16.      * 
  17.      * 单次打开数据库最大时间:也就是最大open的时间,对于某些数据库,长时间open但不使用,不仅会造成资源浪费还会意外断开。在设置了60 * 1000后,且60秒内未进行查询,就自动断开。 
  18.      * 多线程支持:简单的说就是高级点的数据库,比如 MySql 写 JasonQt_Database::QueryMultiMode ;低级的,比如 Sqlite ,写 JasonQt_Database::QuerySingleMode ,就可以了。 
  19.      * 最小等待时间:对于某些数据库,比如Sqlite,密集查询时可能出错,此时可以适当的提升两次查询之间的最小等待时间,比如10ms 
  20.      */  
  21.   
  22.     // Sqlite的连接方式                    类型        连接名        Sqlite文件路径      单次打开数据库最大时间                多线程支持           最小等待时间  
  23.     JasonQt_Database::Control control({ "QSQLITE", "TestDB", "/Users/Jason/test.db" }, { 60 * 1000,        JasonQt_Database::QuerySingleMode, 10});  
  24.   
  25.     // MySql的连接方式                      类型      连接名        IP        数据库    用户名        密码  
  26. //  JasonQt_Database::Control control({ "QMYSQL", "TestDB", "localhost", "JasonDB", "root", "YourPassword" });  
  27.   
  28.   
  29.     // SqlServer的连接方式                  类型      连接名                                    数据库              数据库名   用户名         密码  
  30. //  JasonQt_Database::Control control({ "QODBC", "TestDB", "Driver={SQL SERVER};server=iZ23kn6vmZ\TEST;database=test;uid=sa;pwd=YourPassword;" });  
  31.   
  32.     auto insert = [&]()  
  33.     {  
  34.         auto query(control.query()); // 这里的query在解引用( -> 或者 * )后返回的是 QSqlQuery ,直接用就可以了,不需要单独打开数据库或者其他的初始化  
  35.   
  36.         query->prepare("insert into Test1 values(?)"); // 模拟插入操作  
  37.   
  38.         query->addBindValue(rand() % 1280);  
  39.   
  40.         if(!query->exec())  
  41.         {  
  42.             qDebug() << "Error" << __LINE__;  
  43.         }  
  44.   
  45.         query->clear();  
  46.   
  47.         query->prepare("insert into Test2 values(NULL, ?, ?)");  
  48.   
  49.         query->addBindValue(rand() % 1280);  
  50.         QString buf;  
  51.         for(int now = 0; now < 50; now++)  
  52.         {  
  53.             buf.append('a' + (rand() % 26));  
  54.         }  
  55.         query->addBindValue(buf);  
  56.   
  57.         if(!query->exec())  
  58.         {  
  59.             qDebug() << "Error" << __LINE__;  
  60.         }  
  61.     };  
  62.     auto delete_ = [&]()  
  63.     {  
  64.         auto query(control.query());  
  65.   
  66.         query->prepare("delete from Test1 where data = ?");  
  67.   
  68.         query->addBindValue(rand() % 1280);  
  69.   
  70.         if(!query->exec())  
  71.         {  
  72.             qDebug() << "Error" << __LINE__;  
  73.         }  
  74.   
  75.         query->clear();  
  76.   
  77.         query->prepare("delete from Test2 where data1 = ?");  
  78.   
  79.         query->addBindValue(rand() % 1280);  
  80.   
  81.         if(!query->exec())  
  82.         {  
  83.             qDebug() << "Error" << __LINE__;  
  84.         }  
  85.     };  
  86.     auto update = [&]()  
  87.     {  
  88.         auto query(control.query());  
  89.   
  90.         query->prepare("update Test1 set data = ? where data = ?");  
  91.   
  92.         query->addBindValue(rand() % 1280);  
  93.         query->addBindValue(rand() % 1280);  
  94.   
  95.         if(!query->exec())  
  96.         {  
  97.             qDebug() << "Error" << __LINE__;  
  98.         }  
  99.   
  100.         query->clear();  
  101.   
  102.         query->prepare("update Test2 set data1 = ?, data2 = ? where data1 = ?");  
  103.   
  104.         query->addBindValue(rand() % 1280 + 1);  
  105.         QString buf;  
  106.         for(int now = 0; now < 50; now++)  
  107.         {  
  108.             buf.append('a' + (rand() % 26));  
  109.         }  
  110.         query->addBindValue(buf);  
  111.         query->addBindValue(rand() % 1280 + 1);  
  112.   
  113.         if(!query->exec())  
  114.         {  
  115.             qDebug() << "Error" << __LINE__;  
  116.         }  
  117.     };  
  118.     auto select = [&]()  
  119.     {  
  120.         auto query(control.query());  
  121.   
  122.         query->prepare("select * from Test1 where data = ?");  
  123.   
  124.         query->addBindValue(rand() % 1280);  
  125.   
  126.         if(!query->exec())  
  127.         {  
  128.             qDebug() << "Error" << __LINE__;  
  129.         }  
  130.   
  131.         query->clear();  
  132.   
  133.         query->prepare("select * from Test2 where data1 = ?");  
  134.   
  135.         query->addBindValue(rand() % 1280);  
  136.   
  137.         if(!query->exec())  
  138.         {  
  139.             qDebug() << "Error" << __LINE__;  
  140.         }  
  141.     };  
  142.   
  143.     volatile int count = 0, last = 0;  
  144.   
  145.     QTime time;  
  146.     time.start();  
  147.   
  148.     QThreadPool::globalInstance()->setMaxThreadCount(10);  
  149.   
  150.     for(int now = 0; now < 3; now++) // 开启3个线程测试  
  151.     {  
  152.         QtConcurrent::run([&]()  
  153.         {  
  154.             while(true)  
  155.             {  
  156.                 count++;  
  157.                 if(!(count % 1000))  
  158.                 {  
  159.                     const auto &&now = time.elapsed();  
  160.                     qDebug() << now - last; // 打印每完成1000语句的时间  
  161.                     last = now;  
  162.                 }  
  163.   
  164.                 switch(rand() % 20)  
  165.                 {  
  166.                     case 0:  { insert(); break; }  
  167.                     case 1:  { delete_(); break; }  
  168.                     case 2:  { update(); break; }  
  169.                     default: { select(); break; }  
  170.                 }  
  171.             }  
  172.             QThread::msleep(10);  
  173.         });  
  174.     }  
  175.   
  176.     return a.exec();  
  177. }  

我也写了一个示例工程,可以前往这里下载

http://download.csdn.net/detail/wsj18808050/8566497

http://blog.csdn.net/wsj18808050/article/details/44891715

原文地址:https://www.cnblogs.com/findumars/p/5034637.html